count vs for_each
This is the single most consequential iteration decision in Terraform, and getting it wrong is behind a large share of "Terraform wants to destroy things I didn't change" incidents. The rule is short: use for_each for resources with distinct identities or collections that change, and reserve count for a fixed number of identical resources or a simple on/off toggle.
The choice is not cosmetic and it is not about character count. It decides how each instance is keyed in state, which in turn decides what a future edit churns. Pick count for a list a human will edit, and the first mid-list deletion cascades into destroy/recreate on unrelated resources.
The Core Difference
count identifies each instance by numeric position: index 2 is whatever the third element happens to be right now. for_each identifies each instance by key: ["cron"] is bound to the string "cron" no matter where it sits in the collection. Position is volatile; a key is stable. Everything downstream follows from that one distinction.
The Renumbering Problem
Under count, deleting a middle element shifts every later element's index, and Terraform reads the shift as "these resources changed" — updating and destroying things you never touched. Under for_each, deleting an element removes exactly that element's keyed instance and leaves the rest with identical addresses, so the plan is one clean destroy. The same edit is a surgical removal with keys and a cascade with indices.
count — positional: index N is whatever the Nth element currently is, so editing a list shifts identities and churns unrelated resources. Choose it only for a fixed number of identical resources or an on/off toggle (count = condition ? 1 : 0).
for_each — keyed: each instance is bound to a stable key and is unaffected by changes to other keys. Choose it for everything with identity or any collection that can change over time — which is most things.
The Decision Rule
Ask one question: do these resources have stable identities, or can the collection change anywhere but the end? If yes to either, use for_each. If it is a fixed number of truly interchangeable resources, or a single resource you want present or absent, count is fine. Make for_each the default and treat any count as a deliberate choice you can justify.
Migrating count to for_each
Switching an existing count resource to for_each changes every address from [0] to ["key"], which Terraform reads as destroy-all/create-all unless you tell it the instances are the same. moved blocks map each old indexed address to its new keyed address, so the migration applies as zero changes instead of recreating live infrastructure.
# Before: count = 2 over ["api", "worker"] # After: for_each = toset(["api", "worker"]) moved { from = aws_instance.web[0] to = aws_instance.web["api"] } moved { from = aws_instance.web[1] to = aws_instance.web["worker"] }
Edge Cases
One place count stays idiomatic even when you default to for_each: conditional creation of a single resource. count = var.enabled ? 1 : 0 reads more clearly than for_each = var.enabled ? {...} : {} for a plain on/off, because there is genuinely one optional instance and no identity to preserve. Reach for the for_each toggle only when the conditional set has more than one member.
- Defaulting to
counteverywhere because it is simpler, then suffering destroy/recreate churn the first time a list changes in the middle. - Migrating from
counttofor_eachwithoutmovedblocks, recreating every live resource in the process. - Using a
for_eachmap for a pure on/off toggle wherecount = condition ? 1 : 0is clearer and has no identity to preserve. - Believing the choice is cosmetic — it determines resource identity in state, which is anything but cosmetic.
- Getting a
movedblock'sfrom/toaddresses wrong during migration, so the move doesn't match and Terraform recreates anyway.
- Make
for_eachthe default and justify any use ofcount. - Use
count = condition ? 1 : 0for conditional creation of a single resource. - When migrating
counttofor_each, add amovedblock per instance so existing resources keep their identity. - Decide based on whether elements have stable identities, not on which keyword is fewer characters.
- Verify any migration with a plan that reports zero changes before applying it.
Knowledge Check
What is the fundamental difference between count and for_each?
countidentifies instances by numeric position;for_eachidentifies them by stable keyfor_eachis faster because it parallelizes the instances, whilecountapplies them seriallycountworks on maps andfor_eachworks on lists- They are interchangeable; the choice is purely stylistic
You have a list of named resources a teammate will edit over time. Which do you use?
for_each, so each resource keeps a stable key and mid-list edits stay surgicalcount, because it handles ordered lists more naturally- Either — the resulting state is identical
count, then keep the list sorted alphabetically so the indices never renumber
How do you migrate a count resource to for_each without recreating everything?
- Add a
movedblock mapping each old indexed address to its new keyed address - There is no safe way; the resources must be recreated
- Run
terraform apply -refresh-onlyand Terraform reconciles it - Delete the state file so Terraform re-imports each instance under its new key
When is count still the idiomatic choice?
- Conditional creation of a single resource:
count = var.enabled ? 1 : 0 - Any time the collection is a map of objects keyed by a stable name field
- Whenever you need each instance to carry several configured values
- For large fleets, because
for_eachdegrades past 100 instances
You got correct