Terraform Tutorial — Lists, Maps, and Objects
Beyond strings, numbers, and booleans, Terraform supports collection types — lists, maps, sets, and objects. These let you work with groups of values, which is essential for managing multiple resources, tags, and configuration sets.
Lists (Tuples)
A list is an ordered collection of values:
variable "availability_zones" {
type = list(string)
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
Accessing List Elements
Use zero-based indexing:
# First element
first_az = var.availability_zones[0] # "us-east-1a"
# Last element (using length)
last_az = var.availability_zones[length(var.availability_zones) - 1]
List of Other Types
# List of numbers
variable "ports" {
type = list(number)
default = [80, 443, 8080]
}
# List of booleans
variable "flags" {
type = list(bool)
default = [true, false, true]
}
Maps
A map is a collection of key-value pairs where all keys are strings:
variable "tags" {
type = map(string)
default = {
Name = "web-server"
Environment = "dev"
Team = "backend"
}
}
Accessing Map Values
# Bracket notation
env = var.tags["Environment"] # "dev"
# Dot notation (only works for simple keys without special characters)
env = var.tags.Environment # "dev"
Map of Numbers
variable "instance_counts" {
type = map(number)
default = {
dev = 1
staging = 2
prod = 5
}
}
# Usage
count = var.instance_counts[var.environment]
Objects
An object is like a map but with a defined structure — each key has a specific type:
variable "database_config" {
type = object({
engine = string
engine_version = string
instance_class = string
allocated_storage = number
multi_az = bool
})
default = {
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.micro"
allocated_storage = 20
multi_az = false
}
}
Access object attributes with dot notation:
engine = var.database_config.engine # "postgres"
size = var.database_config.allocated_storage # 20
Sets
A set is like a list but with no duplicates and no guaranteed order:
variable "allowed_cidrs" {
type = set(string)
default = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
}
Sets are useful with for_each (which requires a set or map, not a list). Convert a list to a set with toset():
for_each = toset(var.server_names)
Complex Types
You can nest types to create complex structures:
# List of objects
variable "users" {
type = list(object({
name = string
email = string
admin = bool
}))
default = [
{ name = "alice", email = "alice@example.com", admin = true },
{ name = "bob", email = "bob@example.com", admin = false },
]
}
# Map of lists
variable "security_group_rules" {
type = map(list(number))
default = {
http = [80, 80]
https = [443, 443]
ssh = [22, 22]
}
}
Useful Collection Functions
HCL has built-in functions for working with collections:
length()
Returns the number of elements:
count = length(var.availability_zones) # 3
merge()
Combines two or more maps:
locals {
default_tags = {
ManagedBy = "terraform"
Project = "web-app"
}
environment_tags = {
Environment = var.environment
}
all_tags = merge(local.default_tags, local.environment_tags)
# { ManagedBy = "terraform", Project = "web-app", Environment = "dev" }
}
concat()
Combines two or more lists:
all_subnets = concat(var.public_subnets, var.private_subnets)
keys() and values()
Extract keys or values from a map:
tag_keys = keys(var.tags) # ["Environment", "Name", "Team"]
tag_values = values(var.tags) # ["dev", "web-server", "backend"]
flatten()
Flattens nested lists into a single list:
all_cidrs = flatten([var.public_cidrs, var.private_cidrs])
lookup()
Look up a key in a map with a default fallback:
instance_type = lookup(var.instance_types, var.environment, "t3.micro")
# If var.environment isn't in the map, returns "t3.micro"
What’s Next?
You can now work with collections of values. Next, let’s learn about for expressions — a powerful way to transform and filter collections.