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:
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 mistakeCommon mistake: removing an item from the middle of a
count-based list. If you deleteworker-1from a list that feedscount, 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 fromcounttofor_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:
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:
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:
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:
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
NoteSecurity/safety note: switching an existing resource from
counttofor_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 runterraform planfirst and review carefully; for stateful resources like databases, useterraform state mvto rename the address in state instead of letting Terraform destroy and recreate.
# Renaming state address after switching count to for_each — avoids destroy/recreateterraform 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.