100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
HCL

count and for_each

Compare Terraform's two meta-arguments for creating multiple copies of a resource or module, and understand why for_each is usually the safer choice.

Resource Graph & DependenciesIntermediate9 min readJul 9, 2026
Analogies

count and for_each

By default, a resource or module block in Terraform produces exactly one instance of the object it describes. When you need several similar instances — five EC2 hosts, one S3 bucket per environment, one IAM user per name in a list — you use one of two meta-arguments: count, which creates a fixed number of numerically indexed instances, or for_each, which creates one instance per element of a map or set, keyed by that element rather than by position. Both accomplish multiplication of resources, but they track instance identity in fundamentally different ways, and that difference has real consequences during updates.

🏏

Cricket analogy: Selecting five net bowlers by squad number is like count's fixed quantity, but naming specific players such as Bumrah, Shami, and Siraj on the team sheet mirrors for_each tracking each instance by identity rather than slot.

count: index-based instances

count takes a whole number and creates that many instances of the resource, addressed as resource_type.name[0], resource_type.name[1], and so on. Inside the block, count.index gives the current position, which is commonly used to derive per-instance values from a list. The critical weakness of count is that Terraform identifies instances purely by their numeric index. If you remove an element from the middle of the list count reads from, every instance after that position shifts down by one index, and Terraform sees this as though every shifted resource were destroyed and a different one created in its place — even though conceptually nothing about most of those resources changed.

🏏

Cricket analogy: count.index is like a batting order slot: if you drop the number-3 batter, everyone below shifts up a position, and the scoreboard treats each shifted player as a brand-new entrant even though most of the lineup is unchanged.

hcl
variable "instance_names" {
  type    = list(string)
  default = ["web-a", "web-b", "web-c"]
}

resource "aws_instance" "web" {
  count         = length(var.instance_names)
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"

  tags = {
    Name = var.instance_names[count.index]
  }
}
# Removing "web-a" from the list shifts web-b to index 0 and web-c to index 1,
# causing Terraform to plan a destroy/recreate for both.

for_each: key-based instances

for_each takes a map or a set of strings and creates one instance per key, addressed as resource_type.name["key"]. Because instances are tracked by a stable string key rather than a position, removing one entry from the map only affects the instance for that exact key — every other instance is left completely untouched in the state file and in the real infrastructure. This makes for_each the safer default whenever the members of the collection have a natural, stable identifier such as a name, an environment label, or a region.

🏏

Cricket analogy: for_each keyed by player name is like a scorecard tracking Kohli, Rohit, and Gill by name: drop Rohit and only his row disappears, while Kohli's and Gill's stats stay completely untouched.

hcl
variable "buckets" {
  type = set(string)
  default = ["logs", "backups", "artifacts"]
}

resource "aws_s3_bucket" "this" {
  for_each = var.buckets
  bucket   = "acme-${each.key}-prod"

  tags = {
    Purpose = each.value
  }
}
# Removing "backups" from the set only destroys aws_s3_bucket.this["backups"];
# "logs" and "artifacts" are unaffected.

A useful mental model: count is like numbering seats in a row — pull one seat out and everyone behind it shifts forward. for_each is like assigning name tags — remove one person and everyone else keeps their own tag, unaffected.

You cannot use count and for_each on the same resource or module block simultaneously — Terraform will raise a configuration error. You must choose one, and each block within the same configuration can only use one of the two.

each.key, each.value, and for_each with maps

When for_each is given a map, each.key is the map key and each.value is the corresponding value, letting you drive multiple independent settings per instance instead of just a name. When for_each is given a set of strings, each.key and each.value are identical, since a set has no separate value beyond the string itself. This flexibility makes for_each with a map the natural choice whenever each instance needs more than one differing attribute, such as instance type per environment or CIDR block per subnet.

🏏

Cricket analogy: for_each over a map is like assigning each batter both a name and a batting position — Kohli at number 4, Pant at number 6 — giving two attributes per instance, unlike a plain set of names alone.

  • count creates a fixed number of instances addressed by numeric index (resource[0], resource[1], ...).
  • for_each creates one instance per key in a map or set, addressed by that key (resource["key"]).
  • Removing a middle element from a count list shifts indexes and forces destroy/recreate of unrelated instances.
  • Removing an entry from a for_each map or set only affects that specific instance, leaving others untouched.
  • count and for_each cannot both be used on the same resource or module block.
  • Prefer for_each whenever the collection members have a natural stable identifier.

Practice what you learned

Was this page helpful?

Topics covered

#HCL#TerraformInfrastructureAsCodeStudyNotes#DevOps#CountAndForEach#Count#Each#Index#Based#StudyNotes#SkillVeris