Skip to main content
Terraform for_each vs count - Which Loop to Use

Terraform for_each vs count - Which Loop to Use

Key Takeaway

Compare Terraform for_each and count. Learn when to use each with examples covering index stability, conditional creation, and map-based resource management.

Table of Contents

Quick Answer

Use for_each when resources have meaningful names/keys (stable addressing). Use count for simple “create N copies” or conditional creation (0 or 1). for_each is safer because removing an item doesn’t reindex all other resources.

The Reindexing Problem with count

# count creates resources by index
resource "aws_subnet" "private" {
  count      = 3
  cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
}
# aws_subnet.private[0], aws_subnet.private[1], aws_subnet.private[2]

# If you remove the middle subnet, indexes shift:
# [0] stays, [1] becomes [0]'s old [2] — Terraform destroys and recreates!
# for_each uses keys — no reindexing
resource "aws_subnet" "private" {
  for_each   = toset(["us-east-1a", "us-east-1b", "us-east-1c"])
  cidr_block = cidrsubnet(var.vpc_cidr, 8, index(tolist(toset(["us-east-1a", "us-east-1b", "us-east-1c"])), each.key))
  availability_zone = each.key
}
# aws_subnet.private["us-east-1a"], aws_subnet.private["us-east-1b"], ...

# Removing "us-east-1b" only destroys that one subnet — others untouched

When to Use count

Conditional Resource Creation

variable "create_bastion" {
  type    = bool
  default = false
}

resource "aws_instance" "bastion" {
  count         = var.create_bastion ? 1 : 0
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
}

# Reference: aws_instance.bastion[0].public_ip (when created)

Simple N Copies

resource "aws_instance" "worker" {
  count         = var.worker_count
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.medium"
  tags = { Name = "worker-${count.index}" }
}

When to Use for_each

Named Resources from a Map

variable "subnets" {
  type = map(object({
    cidr = string
    az   = string
    tier = string
  }))
  default = {
    "private-a" = { cidr = "10.0.1.0/24", az = "us-east-1a", tier = "private" }
    "private-b" = { cidr = "10.0.2.0/24", az = "us-east-1b", tier = "private" }
    "public-a"  = { cidr = "10.0.3.0/24", az = "us-east-1a", tier = "public" }
  }
}

resource "aws_subnet" "main" {
  for_each          = var.subnets
  vpc_id            = aws_vpc.main.id
  cidr_block        = each.value.cidr
  availability_zone = each.value.az
  tags              = { Name = each.key, Tier = each.value.tier }
}

Resources from a Set

resource "aws_iam_user" "team" {
  for_each = toset(["alice", "bob", "carol"])
  name     = each.value
}

Comparison Table

Featurecountfor_each
Address formatresource[0], resource[1]resource["key"]
On item removalReindexes — may destroy/recreateOnly removes that item
Input typeNumberSet or map
Conditional creationcount = 0 or 1⚠️ Possible but verbose
Known at plan timeMust know numberMust know keys
Reference syntaxresource[0].idresource["key"].id

Converting count to for_each

# Before (count)
variable "az_list" {
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
resource "aws_subnet" "private" {
  count             = length(var.az_list)
  availability_zone = var.az_list[count.index]
}

# After (for_each)
resource "aws_subnet" "private" {
  for_each          = toset(var.az_list)
  availability_zone = each.key
}

# Migrate state:
# terraform state mv 'aws_subnet.private[0]' 'aws_subnet.private["us-east-1a"]'
# terraform state mv 'aws_subnet.private[1]' 'aws_subnet.private["us-east-1b"]'

Conclusion

Default to for_each for most use cases — it creates stable, key-based resource addresses that don’t shift when items are added or removed. Use count for simple conditional creation (count = var.enabled ? 1 : 0) and fixed-number copies where order doesn’t matter.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.