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.
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.
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
1. How does Terraform address individual instances created with count?
2. Why is for_each generally safer than count when removing an item from the middle of a collection?
3. What input types does for_each accept?
4. What happens if you try to set both count and for_each on the same resource block?
5. Inside a for_each block using a set of strings, what is the relationship between each.key and each.value?
Was this page helpful?
You May Also Like
Dynamic Blocks
Learn how to generate repeated nested configuration blocks programmatically inside a resource, avoiding hand-written repetition for variable-length nested settings.
Implicit vs Explicit Dependencies
Learn how Terraform automatically infers resource ordering from configuration references, and when you must override that inference with depends_on.
Expressions and Functions
HCL expressions let you reference values, perform operations, and transform data using Terraform's extensive built-in function library. This topic covers references, operators, conditionals, and common functions.
Refactoring State with moved Blocks
The moved block lets Terraform track renamed or relocated resources declaratively, avoiding destructive replace plans when refactoring configuration.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics