Explain `for` expressions — how do you transform a list or map into another list or map?
Quick Answer
A `for` expression is HCL's list/map comprehension: `[for s in var.subnets : s.id]` transforms a list into a new list (here, extracting just the `id` from each object), while `{for s in var.subnets : s.name => s.id}` produces a map keyed by name. An optional `if` clause filters elements (`[for s in var.subnets : s.id if s.public]`), and wrapping the whole expression in `{...}` instead of `[...]` is what switches the result from a list to a map. `for` expressions are the standard way to reshape data pulled from one resource/variable into the exact shape another resource or a `for_each` needs.
Detailed Answer
for expressions are HCL's answer to "map over a list" or "build a dictionary from these elements" — the kind of data reshaping that comes up constantly once you're passing computed values from one resource into another's for_each or a structured output.
List-to-list
variable "subnets" {
type = list(object({
id = string
public = bool
}))
}
locals {
subnet_ids = [for s in var.subnets : s.id]
# -> ["subnet-aaa", "subnet-bbb", "subnet-ccc"]
}
Square brackets ([...]) produce a list, with one output element per input element (unless filtered out).
List-to-map
locals {
subnets_by_id = {for s in var.subnets : s.id => s}
# -> { "subnet-aaa" = {...}, "subnet-bbb" = {...}, ... }
}
Curly braces ({...}) with a key => value pair instead produce a map — this single syntax change ([...] vs {...}) is what determines whether you get a list or a map out.
Filtering with if
locals {
public_subnet_ids = [for s in var.subnets : s.id if s.public]
}
The optional trailing if clause skips elements that don't satisfy the condition — here, only including subnets where public == true.
Iterating over maps
locals {
# var.tags is a map(string)
tag_list = [for k, v in var.tags : "${k}=${v}"]
}
When iterating a map, for k, v in ... gives you both the key and value per iteration.
Why this matters practically
for expressions are the standard glue between resources: turning a list of objects a data source returned into the exact map shape a for_each needs, or reshaping a module's raw output into something a consuming resource can use directly — without writing a single line of imperative loop code, since HCL has no for statement, only this expression form.