Chapter 5: Iteration and Conditionals
Topic 31

count vs for_each

Comparison

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.

Positional vs keyed identity
count — positional
Index N is whatever element is Nth right now. Editing the list mid-way renumbers the tail, so unrelated instances churn with destroy/recreate.
for_each — keyed
Each instance is bound to a stable key. Removing one key leaves every other instance's address untouched — no renumbering, no churn.

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 vs for_each

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.

moved blocks turn the migration into a no-op
# 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.

Common Mistakes
  • Defaulting to count everywhere because it is simpler, then suffering destroy/recreate churn the first time a list changes in the middle.
  • Migrating from count to for_each without moved blocks, recreating every live resource in the process.
  • Using a for_each map for a pure on/off toggle where count = condition ? 1 : 0 is 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 moved block's from/to addresses wrong during migration, so the move doesn't match and Terraform recreates anyway.
Best Practices
  • Make for_each the default and justify any use of count.
  • Use count = condition ? 1 : 0 for conditional creation of a single resource.
  • When migrating count to for_each, add a moved block 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.
Comparable tools Pulumi sidesteps the choice with explicit resource names in loops CloudFormation no equivalent distinction Terraform the distinction is core to fluency

Knowledge Check

What is the fundamental difference between count and for_each?

  • count identifies instances by numeric position; for_each identifies them by stable key
  • for_each is faster because it parallelizes the instances, while count applies them serially
  • count works on maps and for_each works 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 surgical
  • count, 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 moved block mapping each old indexed address to its new keyed address
  • There is no safe way; the resources must be recreated
  • Run terraform apply -refresh-only and 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_each degrades past 100 instances

You got correct