Fix Terraform Error: "count" and "for_each" Meta-Arguments Are Mutually Exclusive

beginner๐Ÿ—๏ธ Terraform2026-05-11| Terraform 0.13+, any OS (Linux, macOS, Windows), any cloud provider

Error Message

The "count" and "for_each" meta-arguments are mutually exclusive.
#terraform#count#for_each#meta-argument

The Error

You run terraform plan or terraform apply and hit this immediately:

โ”‚ Error: Invalid combination of "count" and "for_each"
โ”‚
โ”‚   on main.tf line 3, in resource "aws_instance" "web":
โ”‚    3: resource "aws_instance" "web" {
โ”‚
โ”‚ The "count" and "for_each" meta-arguments are mutually exclusive.

A resource block (or module block) has both count and for_each defined at once. Terraform can't decide which iteration strategy to use, so it stops right there.

Why This Happens

Both count and for_each tell Terraform to spin up multiple instances of a resource โ€” but they work in fundamentally different ways:

  • count โ€” creates N copies, referenced by index (resource[0], resource[1], โ€ฆ)
  • for_each โ€” creates one copy per map key or set element, referenced by key (resource["web"], resource["db"], โ€ฆ)

Having both in the same block is contradictory. It's like telling a chef to cook a steak medium-rare and well-done at the same time โ€” one has to go.

Three situations where this tends to bite people:

  • Migrating old count-based code to for_each, but forgetting to delete the original count line
  • Copy-pasting a resource block from another project that used count, then slapping for_each on top without cleanup
  • A module call that receives both as arguments from different sources

Step-by-Step Fix

Step 1: Find the offending block

Terraform's error includes the exact file and line number. Open that file and look for a block with both arguments side by side:

resource "aws_instance" "web" {
  count    = 3                          # โ† remove one of these
  for_each = toset(["a", "b", "c"])    # โ† keep only one

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
}

Step 2: Choose the right meta-argument

Pick the one that actually fits your situation.

Use count when you need N identical (or near-identical) copies and keys don't matter:

resource "aws_instance" "web" {
  count = 3

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  tags = {
    Name = "web-${count.index}"
  }
}

Use for_each when each instance has a distinct role or identity โ€” web, api, db โ€” or you need to reference them by name later:

locals {
  instances = {
    web = "t3.micro"
    api = "t3.small"
    db  = "t3.medium"
  }
}

resource "aws_instance" "servers" {
  for_each = local.instances

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = each.value

  tags = {
    Name = each.key
  }
}

Referencing a specific instance later is clean and readable:

output "web_ip" {
  value = aws_instance.servers["web"].public_ip
}

Step 3: Remove the unused meta-argument

Delete whichever one you're not keeping. Scan the block carefully โ€” a stray count = ... or for_each = ... hiding at the bottom is enough to trigger the error. Each resource should have exactly one (or neither, if you only need a single instance).

Module blocks follow the same rules

Module calls are just as vulnerable:

# Broken
module "vpc" {
  source   = "./modules/vpc"
  count    = 2
  for_each = toset(["us-east-1", "eu-west-1"])
}

# Fixed โ€” pick one:
module "vpc" {
  source   = "./modules/vpc"
  for_each = toset(["us-east-1", "eu-west-1"])

  region = each.value
}

Verify the Fix

Run terraform validate first โ€” it's faster than a full plan and catches syntax issues immediately:

terraform validate

You should see:

Success! The configuration is valid.

Then confirm the plan looks right:

terraform plan

One thing to watch: if you're switching an existing resource from count to for_each, Terraform will plan to destroy and recreate every instance. The state key format changes from resource[0] to resource["key"], and Terraform treats them as different objects. Use terraform state mv to migrate the state manually if you need to avoid recreation.

Quick Tips

  • Lean toward for_each for most real infrastructure. Remove one item from a count-based list mid-range and Terraform will shift all subsequent indexes โ€” potentially destroying and recreating resources you never touched. With for_each, only the deleted key disappears.
  • Empty map or set is fine โ€” for_each = {} creates zero resources without any error.
  • Can't pass a plain list to for_each โ€” wrap it with toset(): for_each = toset(var.names). Lists allow duplicates; for_each doesn't.
  • Dynamic blocks are a different story โ€” using for_each inside a dynamic block within a resource that already uses count is perfectly valid. That's not the conflict Terraform is complaining about.

Related Error Notes