In Terraform, what does adding `for_each` to a `module` block do, and how do you reference the resulting instances and their outputs?
answer
- repeats the whole module, not one resource
- key becomes part of the address
- module.NAME is now a map
- keys must be known at plan time
- module support arrived later than resources
basics
~20 sfor_each on a module block instantiates the entire module once per map key or set element. Each instance is addressed as module.NAME["key"], each.key and each.value are available inside the block, and the module's outputs become a map keyed by those same keys.
solid answer
~50 s`for_each` on a `module` block repeats the whole module — every resource it declares — once per entry of the map or set you give it. Inside the block, `each.key` and `each.value` let you feed per-instance arguments, and each copy gets the address `module.NAME["key"]`, which is what shows up in the plan and in state. The outputs are no longer a flat value: `module.NAME` becomes a map of instances, so you consume them with a `for` expression like `{ for k, m in module.svc : k => m.url }` or with `values(module.svc)[*].url`. Two things to remember: `count` and `for_each` on modules only arrived in Terraform 0.13, long after they worked on resources, and the keys must be known at plan time — deriving them from an attribute that only exists after apply is an error.
code
hcl · 11 linesmodule "service" {
source = "./modules/service"
for_each = var.services
name = each.key
cpu = each.value.cpu
}
output "service_urls" {
value = { for key, instance in module.service : key => instance.url }
}go deeper
Recall that for_each on a module block creates one copy of the whole module per entry, and that you address a copy with a string key in brackets, like module.service["api"].
Be ready to write the output projection from memory — { for k, m in module.svc : k => m.url } — and to explain that the keys must be known at plan time because Terraform needs the instance addresses before planning inside them.
Show that you know the refactor cost: collapsing duplicated module blocks or pushing count down into resources changes every address, so it is a deliberate state move, not a cosmetic cleanup, and needs a plan reviewed before anyone applies it.
Own the interface decision: whether repetition belongs at the module block, inside the module, or in separate root configurations with separate state, and what each choice implies for blast radius, review load and how teams roll out a change one tenant at a time.
## What the meta-argument does On a `resource` block, `for_each` repeats one resource. On a `module` block it repeats *everything the module declares* — every resource, data source and nested module inside it — once per element of the map or set of strings you supply. ```hcl variable "services" { type = map(object({ cpu = number port = number })) } module "service" { source = "./modules/service" for_each = var.services name = each.key cpu = each.value.cpu port = each.value.port } ``` This is the standard way to express "the same stack, N times, parameterised" — one module per tenant, per region-local service, per queue — without copying `module` blocks. ## Addresses and state Each instance is addressed with the key in brackets: `module.service["api"]`, and resources inside it as `module.service["api"].aws_ecs_service.this`. Those addresses appear in plan output, in state, and anywhere you pass an address by hand. Because the key is part of the address, adding or removing an entry in the map touches only that instance — the others keep their identity. That is the same identity property `for_each` gives resources, and it is why maps and sets are used here rather than an ordinal. ## Reading outputs back This is where people trip. Without `for_each`, `module.service.url` is a value. With `for_each`, `module.service` is a **map of module instances**, so a bare `module.service.url` is invalid. You either index one instance or project across all of them: ```hcl output "one_url" { value = module.service["api"].url } output "all_urls" { value = { for key, instance in module.service : key => instance.url } } ``` `values(module.service)[*].url` gives the same list of urls without the keys. With `count` instead of `for_each` the shape is a list — `module.service[0].url`, and `module.service[*].url` for all of them. ## The plan-time constraint The `for_each` argument must be known during plan. If you build the map from something that only exists after apply — a set of ids from a resource being created in the same run, for example — Terraform stops with an error saying the `for_each` value depends on resource attributes that cannot be determined until apply. The reason is structural: Terraform has to know how many instances exist, and their addresses, before it can plan anything inside them. The usual fixes are to key off static configuration you already have (a variable, a list of names) rather than off generated ids, or to split the work into two applies. Values *inside* each instance may be unknown; only the keys must be known. ## Version history matters here `count` and `for_each` worked on resources for years before they worked on modules — module support landed in Terraform 0.13. If you are reading an older codebase you will find the workaround it forced: the same `module` block copy-pasted several times with different names, or `count` pushed down into every resource inside the module. Both are worth recognising in a code review, because collapsing them into a single `for_each` module block changes every resource address and is therefore a state-moving refactor, not a free cleanup. ## The restriction people forget A module that declares its **own** provider configuration cannot be used with `for_each` (nor with `count` or `depends_on`). Terraform needs the provider configurations for a module to be fixed and known before it expands instances, so a module carrying its own `provider` block is incompatible with repetition. If you hit that error, the fix is to remove the provider block from the child module, declare the requirement in `required_providers` — adding `configuration_aliases` if it needs more than one — and pass configurations in from the caller. ## What to say in an interview Name the repetition unit (the whole module), the address shape (`module.NAME["key"]`), the output shape (a map you consume with a `for` expression), the plan-time key requirement, and the 0.13 boundary. That combination shows you have actually operated a repository that uses it rather than read the syntax once.
- Can you use for_each on a module that declares its own provider block?No. A module carrying its own `provider` configuration is incompatible with `count`, `for_each` and `depends_on`, because Terraform must resolve provider configurations before it expands module instances. The fix is to remove the provider block from the child, declare the dependency in `required_providers` — with `configuration_aliases` if it needs a second configuration — and let the caller pass configurations in explicitly.
- What happens if the for_each keys come from ids created in the same apply?Terraform fails at plan time with an error that the `for_each` value depends on attributes that cannot be determined until apply. It must know the instance keys before it can plan what is inside them. Key the loop off static configuration you already hold — names from a variable, entries from a map — or split the work into two applies.
- How does the output shape differ between count and for_each on a module?With `for_each`, `module.NAME` is a map of instances: index it with a string key, or project with a `for` expression or `values(...)`. With `count` it is a list: `module.NAME[0]`, and `module.NAME[*].out` for all of them. The map form is preferable because the key is stable, so adding an entry does not shift the identity of the others.
saying these in an interview costs you the question
- Says module.NAME.output still works with for_each
- Thinks for_each on a module repeats only its root resource
- Builds for_each keys from ids created in the same apply
- Assumes module for_each has existed as long as resource for_each
- Uses count on modules then edits the middle of the list