Skip to main content

Count and For Each

Terraform count and for_each are meta-arguments that create multiple instances of a resource or module from a single block. Count creates N identical copies using a numeric index. For_each creates one instance per item in a map or set, giving each instance a meaningful key instead of a number.

What Are Count and For Each in Terraform?

count and for_each are meta-arguments — special arguments that work on any resource or module block, regardless of its type — that let you create more than one copy of that resource from a single block of code, instead of copy-pasting the same block five times with different names.

Picture it like a print job. count is like printing five identical pages numbered 1 through 5 — same content, just numbered. for_each is like printing one labelled folder per employee name from a list — each copy is tied to something meaningful, not just a number.

At CRED, the platform team needed eight S3 buckets — one per data domain: transactions, rewards, kyc, analytics, and so on. Writing eight separate resource "aws_s3_bucket" blocks would mean eight places to fix a bug later. Using for_each over a list of bucket names, they wrote the block once. Adding a ninth bucket later meant adding one line to a list — not writing a new resource block.

Count — Identical Copies by Number

count takes a number and creates that many copies. Inside the block, count.index gives you the current copy's position, starting at 0:

HCL
resource "aws_instance" "worker" {
count = 3 # creates 3 identical EC2 instances
ami = "ami-0f5ee92e2d63afc18"
instance_type = "t3.medium"
tags = {
Name = "worker-${count.index}" # worker-0, worker-1, worker-2
}
}

Each instance is addressed in state by its index: aws_instance.worker[0], aws_instance.worker[1], aws_instance.worker[2].

Common mistake

Common mistake: removing an item from the middle of a count-based list. If you delete worker-1 from a list that feeds count, Terraform does not just remove that one instance — it shifts every index after it down by one, which means it destroys and recreates everything that came after. This is the single biggest reason engineers switch from count to for_each.

For Each — Keyed Copies from a Map or Set

for_each takes a map or a set of strings. Inside the block, each.key and each.value give you the current item:

HCL
locals {
data_buckets = toset([
"transactions",
"rewards",
"kyc",
"analytics"
])
}
resource "aws_s3_bucket" "domain_data" {
for_each = local.data_buckets # one bucket PER item in the set
bucket = "cred-${each.value}-data-prod" # each.value is the string itself for a set
}

With a map, each.key is the map key and each.value is the map's value — useful when each item needs more than one piece of data:

HCL
locals {
services = {
orders = { cpu = 512, memory = 1024 }
payments = { cpu = 1024, memory = 2048 }
kyc = { cpu = 256, memory = 512 }
}
}
resource "aws_ecs_task_definition" "service" {
for_each = local.services
family = each.key # "orders", "payments", "kyc"
cpu = each.value.cpu # reads the nested cpu value
memory = each.value.memory # reads the nested memory value
}

Each instance is addressed in state by its key, not a number: aws_ecs_task_definition.service["payments"]. Remove "kyc" from the map and only the kyc task definition is destroyed — everything else stays exactly as it is.

When to Use Count vs For Each

Situation Use
You need N identical, interchangeable copies count
Items have meaningful names you want preserved in state for_each
You will ever delete an item from the middle of the list for_each — avoids the index-shift recreate problem
Creating zero or one of a resource conditionally count = var.create_resource ? 1 : 0

Conditional Resources with Count

A very common pattern is using count to create a resource only when a condition is true:

HCL
resource "aws_instance" "bastion" {
count = var.create_bastion_host ? 1 : 0 # 1 if true, 0 if false — creates it or skips it
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
}

Using For Each with Modules

for_each works on module blocks too — useful when you need the same module pattern applied to several environments or services at once:

HCL
module "ecs_service" {
for_each = local.services
source = "./modules/ecs-service"
service_name = each.key
cpu = each.value.cpu
}

Converting Between Count and For Each

Note

Security/safety note: switching an existing resource from count to for_each (or back) is NOT a simple find-and-replace. Terraform sees it as destroying the old indexed resources and creating brand-new keyed ones — because the resource address changes completely. Always run terraform plan first and review carefully; for stateful resources like databases, use terraform state mv to rename the address in state instead of letting Terraform destroy and recreate.

Bash
# Renaming state address after switching count to for_each — avoids destroy/recreate
terraform state mv 'aws_instance.worker[0]' 'aws_instance.worker["orders"]'

Quick Reference

Syntax Refers To
count.index Current copy's number (0, 1, 2...) inside a count block
each.key Current item's map key (or value itself, for a set) inside for_each
each.value Current item's map value inside for_each
resource_type.name[0] Addressing a specific count instance
resource_type.name["key"] Addressing a specific for_each instance
Error Root Cause Fix
Invalid for_each argument Used a list instead of a map or set Wrap the list in toset() first
Plan shows unrelated resources being destroyed Removed an item from the middle of a count list Switch to for_each, or use terraform state mv to fix addressing
each.value is null Map value missing for that key Check every key in your map has a value defined
Duplicate object key Two items resolve to the same map key Make sure your for_each map/set has unique keys

Frequently Asked Questions

Why would I use for_each instead of count for creating multiple resources?

Count indexes instances numerically (`resource[0]`, `resource[1]`), so removing an item from the middle of the source list shifts every subsequent index, causing Terraform to plan destroy-and-recreate operations on resources that didn't conceptually change. For_each keys instances by a map or set value instead, so removing one item only affects that specific key — every other resource's identity stays stable. This makes for_each the safer default whenever the list of items might change over time.

What's a common count/for_each mistake that causes unwanted resource recreation?

Using `count` with a list that isn't guaranteed to be stable-ordered (like the output of a data source or an unsorted list), which can cause Terraform to see every index as changed on the next plan even though the underlying set of resources is the same. Converting the list to a set and using `for_each` fixes this by keying off the actual values rather than position. Also remember: switching an existing resource from count to for_each is a breaking state change and requires `terraform state mv` to avoid destroy/recreate.