There’s a moment in every Terraform project where copy-paste starts to feel wrong. You’re defining the same resource over and over—just with slightly different names or values. That’s usually your cue to reach for for_each.
Terraform’s for_each lets you define multiple instances of a resource using a map or set, giving you better control and readability compared to older approaches like count. If you’ve ever struggled with indexing or resource drift, this is where things start to click.
Why for_each exists (and why count often isn’t enough)
Before for_each, most developers relied on count. It works, but it comes with a catch: resources are tracked by index. Change the order of items, and Terraform might destroy and recreate resources unexpectedly.
Here’s where for_each stands out:
- Resources are identified by keys, not indexes
- Safer updates when data changes
- More readable configurations
Instead of saying “create 3 resources,” you’re saying “create resources based on these named items.” That shift matters.
A quick example: creating multiple AWS S3 buckets
Let’s start with something simple. Imagine you want to create multiple S3 buckets with different names.
1variable "buckets" {
2 default = {
3 logs = "app-logs-bucket"
4 media = "app-media-bucket"
5 backup = "app-backup-bucket"
6 }
7}
8
9resource "aws_s3_bucket" "buckets" {
10 for_each = var.buckets
11
12 bucket = each.value
13
14 tags = {
15 Name = each.key
16 }
17}Here’s what’s happening:
- for_each iterates over a map
- each.key refers to "logs", "media", etc.
- each.value is the actual bucket name
Terraform will create three distinct resources, each tied to its key. No indexing confusion.
Understanding each.key vs each.value
This is one of the most important parts to internalize.
- each.key: The identifier (used in state tracking)
- each.value: The actual data you assigned
If you’re using a set instead of a map, then each.key and each.value will be the same.
Example with a set
1variable "regions" {
2 default = ["us-east-1", "us-west-1"]
3}
4
5resource "aws_vpc" "example" {
6 for_each = toset(var.regions)
7
8 cidr_block = "10.0.0.0/16"
9
10 tags = {
11 Region = each.value
12 }
13}Where for_each really shines
This feature becomes especially powerful when your infrastructure depends on structured data.
Let’s say you’re defining multiple EC2 instances with different configurations:
1variable "instances" {
2 default = {
3 web = {
4 instance_type = "t3.micro"
5 }
6 api = {
7 instance_type = "t3.small"
8 }
9 }
10}
11
12resource "aws_instance" "servers" {
13 for_each = var.instances
14
15 ami = "ami-123456"
16 instance_type = each.value.instance_type
17
18 tags = {
19 Name = each.key
20 }
21}Now your infrastructure is data-driven. Add a new server? Just update the map.
A common mistake developers make
Mixing count and for_each in the same context—or trying to switch between them mid-project—can cause unexpected resource recreation.
Terraform treats count-based and for_each-based resources as fundamentally different. Switching between them often forces destruction and recreation.
If you’re starting fresh, prefer for_each unless you explicitly need indexed behavior.
Referencing resources created with for_each
This is where syntax changes slightly compared to single resources.
Instead of:
1aws_instance.example.idYou’ll use:
1aws_instance.servers["web"].idThe key becomes part of the reference path, which keeps everything explicit and predictable.
Conditional resource creation with for_each
You can combine for_each with filtering logic to control what gets created.
1locals {
2 filtered_instances = {
3 for key, value in var.instances : key => value
4 if value.instance_type == "t3.micro"
5 }
6}
7
8resource "aws_instance" "filtered" {
9 for_each = local.filtered_instances
10
11 ami = "ami-123456"
12 instance_type = each.value.instance_type
13}This pattern is incredibly useful when dealing with large configurations or feature flags.
for_each vs count: a quick mental model
- Use count when resources are identical and order matters
- Use for_each when resources are distinct and named
In most real-world Terraform setups, for_each ends up being the safer and more maintainable choice.
Performance and state considerations
Using for_each doesn’t significantly impact performance, but it does improve state clarity. Each resource is tracked by a stable key, which reduces unintended changes during plan/apply cycles.
However, changing keys in your map will cause Terraform to destroy and recreate those resources. Treat keys as stable identifiers.
When not to use for_each
Despite its flexibility, there are cases where it’s overkill:
- When creating a fixed number of identical resources
- When you don’t need unique identifiers
- When data structure complexity outweighs benefits
Sometimes simplicity wins.
Wrapping it up
Terraform for_each isn’t just about looping—it’s about modeling infrastructure in a way that’s stable, readable, and scalable. Once you start using it with maps and structured variables, your configurations become easier to extend and far less error-prone.
If your Terraform code still relies heavily on repetition, introducing for_each is one of the quickest ways to clean it up and make it production-ready.