count, for_each, and Identity

Scenario: Deleting the very first item from a numerically-counted list of servers makes Terraform propose updating or even replacing every single server that came after it in the list - not just removing the one that was deleted.

When you need more than one instance of a resource, Terraform gives you two repetition mechanisms, and choosing the wrong one is one of the most common real-world Terraform mistakes. count = 3 creates three instances addressed by position: resource.name[0], resource.name[1], resource.name[2]. This works fine as long as the instances are truly interchangeable and nothing in the middle of the list ever gets removed - but the moment you delete item [0], everything shifts down one position, and Terraform sees [1] now pointing at what used to be a different object, proposing changes across the board even though logically only one item should have been removed.

for_each solves this by keying each instance with a stable string you choose - typically a map's keys or a set's values - so resource.name["blue"] always refers to "blue" specifically, regardless of what other keys exist or get removed. That string key literally becomes part of the object's permanent address in state, so pick something durable and meaningful (a service name, an environment name) - never something that might reasonably change later, like a display label a marketing team could rename. As a side note, dynamic blocks (repeating a nested block, like multiple ingress rules inside one security group resource) are a different, unrelated feature - they don't replace resource-level for_each.

Analogy: Numbered coat hooks in a row work fine until someone unscrews hook zero - now every coat shifts one peg over and the attendant has to relabel every ticket. Named, individually-labeled lockers have no such problem: removing "Locker: Alice" never disturbs "Locker: Bob", no matter how many other lockers come and go.

A worked configuration

variable "servers" { type = map(object({ size = string })) }
resource "fakecloud_server" "node" {
  for_each = var.servers
  name     = each.key
  size     = each.value.size
}

If you inherit a count-based configuration and want to migrate it to for_each for exactly this reason, do not just swap the syntax and re-apply - map every existing numbered address (resource.name[0]) to its new string-keyed address (resource.name["blue"]) using moved blocks or explicit state moves first. Skipping that step turns a purely cosmetic refactor into a plan that destroys and recreates your entire fleet.

Common mistakes in production

Goal: Treat the plan as a change contract: understand every create, update, replacement, and destroy before approving it. Reinforce this lesson in the tf-count-for-each Terraform lab. Open /labs/terraform and choose slug tf-count-for-each; the lab runs real Terraform against the offline FakeCloud provider.