Collection Functions: Maps, Lists and Transformations
Scenario
You're managing tags across multiple resources, flattening nested lists of subnets, looking up values from configuration maps, and handling defaults for optional values. These are the bread-and-butter collection operations you'll use in every Terraform config.
Your Objectives
- Use merge() to combine multiple maps (tags).
- Use flatten() to collapse a list of lists.
- Use lookup() to get a map value with a default fallback.
- Use keys() and values() to extract map keys and values.
- Use coalesce() to pick the first non-null/non-empty value.
- Use contains() to check list membership.
Additional Context
Collection functions operate on the three collection types: list, map, and set. They're pure functions — they return a new value without modifying the input.
main.tf
terraform {
required_version = ">= 1.5.0"
}
variable "instance_sizes" {
type = map(string)
default = {
dev = "t3.micro"
staging = "t3.small"
prod = "t3.large"
}
}
variable "override_region" {
type = string
default = ""
}
locals {
# ─── merge(): combine multiple maps ───
# Later maps override earlier ones for duplicate keys
default_tags = { ManagedBy = "terraform", Team = "platform" }
env_tags = { Environment = "dev", CostCenter = "eng-123" }
all_tags = merge(local.default_tags, local.env_tags)
# Result: { ManagedBy = "terraform", Team = "platform", Environment = "dev", CostCenter = "eng-123" }
# ─── flatten(): collapse nested lists ───
subnet_groups = [["10.0.1.0/24", "10.0.2.0/24"], ["10.0.3.0/24"]]
all_subnets = flatten(local.subnet_groups)
# Result: ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
# ─── lookup(): get value from map with default ───
dev_size = lookup(var.instance_sizes, "dev", "t3.micro") # "t3.micro"
unknown_size = lookup(var.instance_sizes, "dr", "t3.nano") # "t3.nano" (default)
# ─── keys() and values(): extract from map ───
env_names = keys(var.instance_sizes) # ["dev", "prod", "staging"]
sizes = values(var.instance_sizes) # ["t3.micro", "t3.large", "t3.small"]
# ─── coalesce(): first non-empty, non-null value ───
region = coalesce(var.override_region, "us-east-1")
# If override_region = "" → returns "us-east-1"
# ─── contains(): check if list includes a value ───
has_prod = contains(keys(var.instance_sizes), "prod") # true
has_dr = contains(keys(var.instance_sizes), "dr") # false
# ─── element(): get list item by index (wraps around) ───
first_subnet = element(local.all_subnets, 0) # "10.0.1.0/24"
# ─── zipmap(): create map from two lists ───
labels = ["name", "env", "region"]
vals = ["myapp", "dev", "us-east-1"]
zipped = zipmap(local.labels, local.vals)
# Result: { name = "myapp", env = "dev", region = "us-east-1" }
# ─── length(): count elements ───
tag_count = length(local.all_tags)
subnet_count = length(local.all_subnets)
}outputs.tf
output "merged_tags" { value = local.all_tags }
output "flattened" { value = local.all_subnets }
output "lookup_dev" { value = local.dev_size }
output "lookup_unknown"{ value = local.unknown_size }
output "env_names" { value = local.env_names }
output "region" { value = local.region }
output "has_prod" { value = local.has_prod }
output "has_dr" { value = local.has_dr }
output "zipped" { value = local.zipped }
output "tag_count" { value = local.tag_count }
output "subnet_count" { value = local.subnet_count }Workflow Commands
terraform init && terraform apply
# Override the region
terraform apply -var="override_region=eu-west-1"
terraform output -json
✓ Expected Outputs
env_names = tolist(["dev", "prod", "staging"])
flattened = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
has_dr = false
has_prod = true
lookup_dev = "t3.micro"
lookup_unknown = "t3.nano"
merged_tags = {
"CostCenter" = "eng-123"
"Environment" = "dev"
"ManagedBy" = "terraform"
"Team" = "platform"
}
region = "us-east-1"
subnet_count = 3
tag_count = 4
zipped = { "env" = "dev", "name" = "myapp", "region" = "us-east-1" }
