Terraform count vs for_each: A Friendly Guide (+ Cheatsheet)
Choosing between count and for_each in Terraform looks simple at first, but it can change how predictable your infrastructure is over time. I’ve been bitten by both approaches in the past, so here’s a practical guide to when each one makes sense, with examples and a quick cheatsheet.
TL;DR
If the identity of each item matters (like named users, buckets, or environments), go with
for_each.If you just need N identical replicas,
countis simpler.Lists +
countare fragile if order changes.for_eachuses stable keys, so adding/removing items is safer.
How I think about it
It comes down to how you reference the resource:
count→ you reference by index, e.g.aws_instance.web[0].for_each→ you reference by a key, e.g.aws_instance.web["blue"].
If humans will ever care about “which one is which,” you probably want for_each.
Quick compare
| Question | Go with |
|---|---|
Do I need a fixed number of replicas (3 web servers)? |
|
Do I have named items (blue/green/canary)? |
|
Will I add/remove items over time? |
|
Do I need stable references in outputs and modules? |
|
Is my input just a simple list without keys? |
|
Examples
Using count
variable "names" { type = list(string) }
resource "aws_instance" "web" {
count = length(var.names)
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
tags = { Name = var.names[count.index] }
}
output "first_web_name" { value = var.names[0] }
output "first_web_id" { value = aws_instance.web[0].id }Good when: you just need copies.
Gotcha: if you insert a new element at the start of the list, Terraform thinks everything shifted and wants to recreate.
Using for_each
variable "servers" {
type = map(object({ size = string }))
}
resource "aws_instance" "web" {
for_each = var.servers
ami = data.aws_ami.amazon_linux.id
instance_type = each.value.size
tags = { Name = each.key }
}
## output "ids_by_name" { value = { for k, r in aws_instance.web : k => r.id } }Good when: items have names and you expect to add/remove them later.
Gotcha: if you rename a key, Terraform sees it as a delete + create.
Pitfalls I’ve hit
Reordering with
count→ surprise destroys. Fix: avoid reordering, or switch tofor_each.Using
for_eachwith a list → unstable numeric keys. Fix: turn list into a map with meaningful keys.Changing keys in
for_each→ causes churn. Treat keys like permanent IDs.
Converting a list to a map for for_each
variable "names" { type = list(string) }
locals {
servers = { for n in var.names : n => { size = "t3.micro" } }
}
resource "aws_instance" "web" {
for_each = local.servers
instance_type = each.value.size
}Migrating safely
If you’re moving from count to for_each, the trick is to line up existing indices with the keys you want, then run terraform state mv so Terraform doesn’t recreate everything.
terraform state mv 'aws_instance.web[0]' 'aws_instance.web["blue"]'
terraform state mv 'aws_instance.web[1]' 'aws_instance.web["green"]'Patterns I’ve seen work
✅ Use
countfor symmetric replicas (workers, identical copies).✅ Use
for_eachfor things that have names or identities (users, buckets, environments).❌ Avoid
countif you’ll keep changing the list.❌ Avoid
for_eachwith volatile or generated keys.
FAQ
Why did Terraform want to recreate my
countresources?* Because your list changed order, and withcountthe index is the identity.Can a resource use both?* No, it’s either
countorfor_each.How should I expose results?* With
for_each, outputs as maps ({ name ⇒ id }). Withcount, outputs as lists (but note that order matters).
Handy checklist
Do humans care about which item is which? →
for_eachWill I add/remove items later? →
for_eachAre they just identical copies? →
countDo outputs need stable keys? →
for_each
Related reads
Terraform state & addressing basics
Handling drift &
terraform importDesigning good module inputs (maps vs lists)