When would you use for_each instead of count in Terraform, and what breaks when you choose wrong?
Quick Answer
Use for_each when resources are identified by a meaningful key (like a name or environment), and count when you need N identical copies. count uses numeric indices, so inserting or removing an item in the middle shifts all subsequent indices, causing Terraform to destroy and recreate resources. for_each uses map keys, so additions and removals only affect the specific resource.
Detailed Answer
Think of assigned seating at a dinner. count is like numbering seats 1 through 10 — if guest #3 cancels, everyone from seat 4 onward shifts down, causing chaos with place cards and meal orders. for_each is like name tags on seats — removing 'Alice' only affects Alice's seat, and everyone else stays put. The difference seems minor until you have production infrastructure attached to those seats.
count creates resources indexed by number: aws_subnet.private[0], aws_subnet.private[1], aws_subnet.private[2]. If you remove the second item from your list, what was index [2] becomes [1], and Terraform sees this as 'destroy [2] and modify [1]' — which means destroying and recreating a subnet that might have running pods on it. This is catastrophic for stateful resources.
for_each creates resources indexed by string key: aws_subnet.private["us-east-1a"], aws_subnet.private["us-east-1b"], aws_subnet.private["us-east-1c"]. If you remove us-east-1b, Terraform only destroys that one subnet. The others are untouched because their keys did not change. This makes for_each the safe default for any resource that has a natural identifier.
Use count only for truly identical resources where the number matters but individual identity does not — like creating N replicas of a test instance that can be destroyed and recreated freely. Use for_each for everything else: subnets (keyed by AZ), IAM users (keyed by name), security group rules (keyed by port or CIDR), DNS records (keyed by hostname), and S3 buckets (keyed by purpose). Converting from count to for_each on existing resources requires terraform state mv to remap indices to keys, or use moved blocks.
The non-obvious gotcha is that for_each requires a set or map — not a list. If you pass a list, you must convert it with toset(). Also, the for_each key becomes part of the resource address, so changing a key destroys and recreates that resource. Choose stable, meaningful keys that will not change over the resource's lifetime. Using the AZ name as a key for subnets is stable. Using an auto-generated ID is not.