for Expressions
A for expression transforms one collection into another — mapping, filtering, and reshaping lists and maps inline. [for s in var.names : upper(s)] builds a new list; { for k, v in var.map : k => v.cidr } builds a map. This is HCL's functional data-transformation tool, and it is how you derive the structures that feed for_each, dynamic blocks, and outputs without an imperative loop.
The bracket you open decides the output type: square brackets produce a list or tuple, curly braces produce an object or map. Everything else — filtering, key reshaping, grouping — is a variation on those two forms.
List and Map Comprehensions
[for ...] yields a list; {for ...} yields a map and requires a key => value arrow. Iterating a list gives you one variable (the element); iterating a map gives you two (key and value). The expression after the colon is evaluated for every element to build the result.
# List: uppercase every name locals { upper_names = [for n in var.names : upper(n)] } # Map: name => its CIDR, from a map of objects locals { cidrs = { for k, v in var.subnets : k => v.cidr } }
Filtering with if
An optional if clause at the end keeps only the elements that match, so you filter inline instead of post-processing with extra functions. The condition runs per element and drops the ones that evaluate false — building exactly the subset you want in a single pass.
locals { public_ids = { for k, v in var.subnets : k => v.id if v.public } }
Transforming Keys and Values
Because a {for} expression lets you write any key and any value, you can re-key a collection entirely — turning a list of objects into a map keyed by one of their fields, the classic move for feeding for_each. { for s in var.servers : s.name => s } takes a list and produces a map keyed by each server's name, giving every element the stable key for_each demands.
Grouping Mode
Adding ... after the value switches a {for} into grouping mode: instead of one value per key, it collects every value that maps to a key into a list under that key. This is how you bucket items — grouping subnets by availability zone, or rules by protocol — without a manual merge.
locals { by_az = { for k, v in var.subnets : v.az => k... } # { "us-east-1a" = ["public"], "us-east-1b" = ["private"] } }
Feeding for_each
for_each needs a map or set with stable keys, and a for expression is how you build it from whatever shape your input arrives in. Take a list of objects, project it into a map keyed by a name field, and hand that straight to for_each. Building the map up front — rather than passing a list and converting awkwardly downstream — keeps the keys explicit and the plan readable.
- Producing a map with duplicate keys from a
forexpression, silently collapsing multiple entries into one without an error. - Building a list when
for_eachneeds a map or set, then converting awkwardly downstream instead of keying the data up front. - Writing one deeply nested
forexpression that would read far clearer as a named local plus a second expression. - Forgetting the
ifclause and filtering after the fact with extra functions, doing in two passes what one would do. - Reaching for grouping mode (
...) when you actually wanted one value per key, getting lists where you expected scalars.
- Use
forexpressions to shape data into exactly the structurefor_eachor a dynamic block needs. - Filter inline with the
ifclause rather than post-processing the result. - Break a complicated comprehension into named locals so each step reads on its own.
- Build maps, not lists, when the result feeds
for_each— the keys become the resource identities. - Reach for grouping mode (
...) only when you genuinely want multiple values bucketed under one key.
Knowledge Check
What determines whether a for expression produces a list or a map?
- The brackets:
[for ...]yields a list,{for ...}yields a map - Whether the input is a list or a map — the output always matches the input
- A
typeargument you pass to the expression - It is always a tuple; you convert it afterward
What does the optional if clause in a for expression do?
- Includes only the elements for which the condition is true, filtering inline
- Branches the entire expression between two distinct output collections, like a ternary
- Stops iteration early at the first false element
- Marks the result as conditional so it is recomputed each apply
What does the ... grouping mode in a {for} expression produce?
- A map where each key holds a list of every value that mapped to it
- A single flattened list of all the values, dropping the keys entirely
- A set with duplicates removed
- The last value for each key, overwriting earlier ones
Why reshape a list of objects into a map before passing it to for_each?
for_eachneeds stable keys, and a map keyed by a name field gives each instance one- Lists are not valid inputs to any iteration expression anywhere in HCL
- Maps are indexed internally, so they apply measurably faster than equivalent lists during the plan phase
- It is required only when there are more than ten elements
You got correct