for_each Meta Argument in Terraform Explained
The for_each meta argument in Terraform is the preferred way to create multiple resources when each resource has a unique identity.
Many Terraform beginners start with count because it is simple. However, as infrastructure grows, count can create unnecessary resource replacements when items are added, removed, or reordered. That's where for_each becomes a better choice.
If you're new to Terraform meta arguments, start with our guide to Terraform Meta Arguments.
Why Use the for_each Meta Argument in Terraform?
Terraform provides multiple ways to create resources dynamically. The two most common approaches are:
countfor_each
While both can create multiple resources, they track resources differently.
countuses numeric indexes.for_eachuses unique keys.
Because of this difference, for_each provides more predictable infrastructure management and reduces the risk of unexpected resource replacement.
Why Terraform Introduced the for_each Meta Argument
The Problem with count
The count meta argument creates resources based on a numeric value.
For example:
resource "local_file" "pet" {
count = 3
filename = "/tmp/file-${count.index}.txt"
content = "Terraform"
}
This works well when resources are identical.
The problem appears when resources have meaningful identities such as:
- EC2 instance names
- IAM usernames
- S3 bucket names
- Kubernetes namespaces
Imagine managing 50 IAM users using a list. If the first user is removed, Terraform shifts the remaining indexes and may plan changes for resources that haven't actually changed.
This behavior becomes difficult to manage in production environments.
For a deeper understanding of index-based resource creation, see our guide on the count meta argument in Terraform.
How for_each Solves the Problem
The for_each meta argument tracks resources using unique keys instead of numeric indexes.
Because Terraform associates each resource with a stable key, adding or removing items affects only the corresponding resource.
This makes infrastructure changes easier to review and significantly safer.
According to the official Terraform Language Documentation, for_each accepts either:
- Maps
- Sets of strings
How to Use for_each with Sets and Maps
Basic for_each Syntax
The following example creates one file for each filename in a set:
resource "local_file" "pet" {
for_each = toset(var.filenames)
filename = each.value
content = "Generated by Terraform"
}
Terraform creates a separate resource instance for every value in the collection.
In this configuration:
for_eachiterates through the set.each.keyrepresents the current key.each.valuerepresents the current value.
When working with sets, the key and value are typically the same.
Converting Lists to Sets for for_each
Why Lists Cannot Be Used Directly
Terraform does not allow lists to be passed directly to for_each.
For example, this will fail:
for_each = var.filenames
If filenames is defined as a list, Terraform returns a validation error.
Option 1: Define the Variable as a Set
A cleaner approach is to declare the variable as a set from the beginning:
variable "filenames" {
type = set(string)
default = [
"/root/pets.txt",
"/root/dogs.txt",
"/root/cats.txt"
]
}
This works well when duplicate values should not exist.
Option 2: Convert a List Using toset()
If the input arrives as a list, convert it using the toset() function:
for_each = toset(var.filenames)
The toset() function removes duplicates and converts the collection into a format supported by for_each.
You can learn more about collection functions in our guide to important Terraform functions.
Using for_each with Maps
Maps are one of the most common ways to use for_each.
Example:
variable "instances" {
default = {
frontend = "t3.micro"
backend = "t3.small"
}
}
resource "aws_instance" "server" {
for_each = var.instances
ami = "ami-xxxxxxxx"
instance_type = each.value
tags = {
Name = each.key
}
}
Terraform creates:
- One EC2 instance named
frontend - One EC2 instance named
backend
Each instance keeps its identity even when other instances are added or removed.
Real-World Example: Managing IAM Users
Consider an IAM user configuration.
Using count:
resource "aws_iam_user" "users" {
count = length(var.usernames)
name = var.usernames[count.index]
}
Terraform tracks resources like this:
aws_iam_user.users[0]
aws_iam_user.users[1]
aws_iam_user.users[2]
If one username is removed from the beginning of the list, Terraform may shift indexes and plan changes for multiple resources.
Now compare that with for_each:
resource "aws_iam_user" "users" {
for_each = toset(var.usernames)
name = each.value
}
Terraform tracks resources like this:
aws_iam_user.users["john"]
aws_iam_user.users["alice"]
aws_iam_user.users["bob"]
Each user keeps its own identity.
Removing "john" only affects John's resource. The remaining users stay untouched.
This behavior is one of the biggest reasons why many production Terraform codebases prefer for_each.
for_each vs count: Which Should You Use?
The answer depends on how resources are identified.
Use for_each when:
- Resources have unique names.
- You are working with maps or sets.
- Stable resource identities matter.
- Resources may be added or removed over time.
- You want safer infrastructure updates.
Use count when:
- Resources are nearly identical.
- You only need a fixed number of instances.
- Resource identity is not important.
- Numeric indexing is acceptable.
For most cloud infrastructure workloads, for_each is usually the safer long-term choice.
How Resource Addresses Differ Between count and for_each
When Terraform creates resources using count, it stores them by index:
aws_instance.web[0]
aws_instance.web[1]
aws_instance.web[2]
When Terraform creates resources using for_each, it stores them by key:
aws_instance.web["frontend"]
aws_instance.web["backend"]
The second approach is easier to understand and less likely to cause accidental infrastructure changes.
Common Mistakes When Using for_each
Using Lists Directly
Terraform requires a map or set.
Convert lists using:
toset(var.values)
Assuming Keys Can Change Safely
The key becomes part of the resource address.
Changing a key often causes Terraform to replace the resource because Terraform sees it as a different object.
Ignoring Existing State During Migration
Moving from count to for_each in an existing project requires extra care.
Changing resource addresses without migrating state can cause Terraform to recreate resources.
Pro Tip
Before converting existing resources from
counttofor_each, review your Terraform state carefully. In many cases, you'll need theterraform state mvcommand to preserve resource mappings and avoid unnecessary replacement.
You can learn more about state management in:
- Introduction to Terraform State
- Terraform State Commands
- Remote State and State Locking in Terraform
Best Practices for Using for_each
Follow these recommendations:
- Use meaningful keys.
- Prefer maps when additional metadata is required.
- Avoid changing keys frequently.
- Keep resource identities stable.
- Review plans carefully before applying changes.
- Use version control for Terraform configurations.
If your resources depend on one another, understanding Terraform resource dependencies will help you avoid deployment issues.
When Should You Avoid for_each?
for_each is not always necessary.
If you simply need three identical resources and resource identity does not matter, count may be easier to read.
Choose the approach that best matches how Terraform should track the resources over time.
Conclusion
The for_each meta argument gives Terraform a reliable way to manage collections of resources using stable identifiers.
Unlike count, which relies on numeric indexes, for_each tracks resources using keys. This reduces unnecessary replacements and makes infrastructure changes easier to understand.
Whether you're managing IAM users, EC2 instances, S3 buckets, or Kubernetes resources, for_each is usually the better option when each resource has its own identity.
For additional examples and supported providers, explore the official Terraform Registry and the Terraform for_each documentation.
Frequently Asked Questions (FAQs)
What is the difference between count and for_each in Terraform?
count creates resources using numeric indexes, while for_each creates resources using unique keys from maps or sets. This makes for_each more stable when resources are added or removed.
Can I use for_each with a list?
No. Terraform requires a map or a set of strings.
Convert lists using:
toset(var.values)
What data types work with for_each?
Terraform supports:
- Maps
- Sets of strings
These collection types allow Terraform to uniquely identify each resource.
Why is for_each preferred over count?
for_each avoids index shifting problems that occur with count, resulting in safer infrastructure updates and fewer unintended resource replacements.
Can I use for_each with Terraform modules?
Yes.
Terraform supports for_each on modules, allowing you to create multiple module instances from a map or set while preserving unique identities.
Can I migrate from count to for_each?
Yes, but you should carefully review Terraform state before making the change.
In many situations, state migration commands such as terraform state mv are required to prevent resource recreation.
8 free, 100% client-side tools for developers — no signup, no data uploads.
Explore all tools