Crisp answer: count creates N identical copies of a resource indexed
numerically. for_each creates one resource per element of a map or set,
keyed by the element. Prefer for_each for anything that might have items
removed — it avoids index shifting.
count:
resource "aws_instance" "web" {
count = 3
ami = "ami-0abc123"
instance_type = "t3.micro"
tags = {
Name = "web-${count.index}" # web-0, web-1, web-2
}
}
# References:
# aws_instance.web[0].id
# aws_instance.web[1].private_ip
# aws_instance.web[*].id (all IDs as a list)
# Conditional creation (count = 0 means don't create)
resource "aws_cloudwatch_log_group" "app" {
count = var.enable_logging ? 1 : 0
name = "/app/logs"
}
# Reference: aws_cloudwatch_log_group.app[0].arn (if it exists)
# Or use: one(aws_cloudwatch_log_group.app[*].arn)
The count index shifting problem:
# var.users = ["alice", "bob", "carol"]
resource "aws_iam_user" "users" {
count = length(var.users)
name = var.users[count.index]
}
# Creates: users[0]=alice, users[1]=bob, users[2]=carol
# Remove "bob" from var.users = ["alice", "carol"]
# Terraform sees:
# users[0]=alice (unchanged)
# users[1]=carol (was "bob" — must destroy bob, create carol) ← unwanted
# users[2] must be destroyed ← unwanted
for_each — the solution:
resource "aws_iam_user" "users" {
for_each = toset(var.users) # convert list to set
name = each.key
}
# Creates: users["alice"], users["bob"], users["carol"]
# Remove "bob": only users["bob"] is destroyed. alice and carol untouched.
# Map example:
resource "aws_subnet" "private" {
for_each = {
"eu-west-2a" = "10.0.1.0/24"
"eu-west-2b" = "10.0.2.0/24"
}
vpc_id = aws_vpc.main.id
availability_zone = each.key # "eu-west-2a"
cidr_block = each.value # "10.0.1.0/24"
tags = {
Name = "private-${each.key}"
}
}
# References: aws_subnet.private["eu-west-2a"].id
Rules:
- Cannot use both
countandfor_eachon the same resource for_eachvalues must be known at plan time (no dynamic unknown values)countcan use a simple number computed at plan time
What to say in the interview:
"count gives you N copies indexed 0 to N-1. for_each gives you one resource per element of a map or set, keyed by the element. The reason to prefer for_each: if you use count with a list and remove an element from the middle, all resources after that index get shifted and Terraform destroys and recreates them unnecessarily. for_each uses stable string keys so removing one element only affects that one resource. I use count for simple conditional creation (count = 0 or 1) and for_each for everything else that iterates over a collection."