Terraform Associate
Deep Dive
Count and for_each create multiple instances of a resource. For expressions and conditionals let you transform data. Built-in functions are essential tools for everyday work and the exam.
Contents
count creates N instances of the resource, indexed from 0 to N-1. Each instance is identified by its numeric index.
# Create 3 EC2 instances
resource "aws_instance" "web" {
count = 3 # integer number
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-${count.index}" # count.index: 0, 1, 2
}
}
# Address of each instance:
# aws_instance.web[0]
# aws_instance.web[1]
# aws_instance.web[2]
# Access in outputs or references:
output "web_ids" {
value = aws_instance.web[*].id # splat: list of all the IDs
}
# Use a variable for the count:
variable "instance_count" { default = 3 }
resource "aws_instance" "app" {
count = var.instance_count
# ...
}
# Conditional creation (count = 0 = don't create, count = 1 = create):
resource "aws_eip" "nat" {
count = var.create_nat_gateway ? 1 : 0
domain = "vpc"
}for_each creates one instance for each element of a set or map. Each instance is identified by its key (string), not by a numeric index.
# for_each with a SET of strings
resource "aws_iam_user" "team" {
for_each = toset(["alice", "bob", "carol"]) # converts list to set
name = each.key # each.key == the set element
}
# Address of each instance:
# aws_iam_user.team["alice"]
# aws_iam_user.team["bob"]
# aws_iam_user.team["carol"]
# for_each with a MAP (key → object)
variable "buckets" {
default = {
logs = { region = "us-east-1", versioning = true }
backups = { region = "us-west-2", versioning = false }
}
}
resource "aws_s3_bucket" "storage" {
for_each = var.buckets
bucket = "company-${each.key}" # each.key = "logs" or "backups"
}
resource "aws_s3_bucket_versioning" "storage" {
for_each = var.buckets
bucket = aws_s3_bucket.storage[each.key].id # reference with the same key
versioning_configuration {
status = each.value.versioning ? "Enabled" : "Suspended"
}
}
# Splat does not work with for_each — use a for expression instead:
output "bucket_ids" {
value = { for k, v in aws_s3_bucket.storage : k => v.id }
}| Feature | count | for_each |
|---|---|---|
| Accepted value type | number | set(string) or map(any) |
| Instance identifier | Numeric index (0, 1, 2…) | String key ("alice", "logs"…) |
| Address in state | resource[0], resource[1] | resource["key"] |
| Element access | count.index (integer) | each.key (key) + each.value (value) |
| What happens when deleting the 2nd of 3 | ⚠️ Terraform renames the 3rd to the 2nd and DESTROYS+RECREATES | ✅ Only destroys the deleted element, the others do not change |
| When to use it | Identical resources with no identity of their own (replicas) | Resources with a unique identity (users, named buckets) |
| Recommendation | Simple cases or conditional creation (0 or 1) | Preferred for most multi-resource cases |
⚠️ The problem with count and changing lists
If you have count = 3 with instances [0]=alice, [1]=bob, [2]=carol and you remove bob (the middle element), Terraform sees: [0]=alice, [1]=carol. Since [1] changed from "bob" to "carol", it destroys and recreates resource [1]. With for_each, each resource has a stable key, and removing "bob" only destroys that resource without affecting the others.
# for expression — generates lists or maps
# [for <item> in <collection> : <expression>]
# {for <key>, <val> in <map> : <new_key> => <new_val>}
variable "users" {
default = [
{ name = "alice", admin = true },
{ name = "bob", admin = false },
]
}
# List of names
locals {
names = [for u in var.users : u.name]
# result: ["alice", "bob"]
# Only the admins (with if condition)
admin_names = [for u in var.users : u.name if u.admin]
# result: ["alice"]
# Map name → admin
user_map = {for u in var.users : u.name => u.admin}
# result: {alice = true, bob = false}
}
# Conditional expression (ternary)
# condition ? value_if_true : value_if_false
resource "aws_instance" "web" {
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
}💡 For expressions vs for_each
For expressions ([for x in list : x.name]) transform collections into locals or values — they do not create resources. The for_each meta-argument creates multiple instances of a resource. They are different concepts despite the similar-sounding names.
Terraform includes dozens of built-in functions. These are the most important ones for the exam, organized by category.
String
format(fmt, ...args)
Formats a string printf-style: format("Hello, %s!", "world")
join(sep, list)
Joins a list into a string: join(",", ["a","b"]) → "a,b"
split(sep, str)
Splits a string into a list: split(",", "a,b") → ["a","b"]
upper(str) / lower(str)
Converts to upper/lower case
trimspace(str)
Removes leading and trailing whitespace and newlines
replace(str, sub, new)
Replaces a substring: replace("hello", "l", "r") → "herro"
Collection
length(list|map|str)
Number of elements: length(["a","b"]) → 2
concat(list1, list2)
Combines lists: concat(["a"],["b"]) → ["a","b"]
flatten(list)
Flattens nested lists: flatten([["a"],["b","c"]]) → ["a","b","c"]
merge(map1, map2)
Combines maps (map2 overrides map1 keys)
lookup(map, key, def)
Looks up a key in a map with a default: lookup({a=1}, "b", 0) → 0
toset(list)
Converts a list to a set (removes duplicates, needed for for_each)
keys(map) / values(map)
List of keys or values of a map
contains(list, val)
True if the list contains the value: contains(["a","b"],"a") → true
Encoding
jsonencode(value)
Converts an HCL value to a JSON string: jsonencode({a="b"}) → {"a":"b"}
jsondecode(str)
Parses a JSON string into an HCL type
base64encode(str)
Encodes a string in Base64
base64decode(str)
Decodes a Base64 string
file(path)
Reads the contents of a file as a string
templatefile(path, vars)
Renders a .tftpl template with variables
Numeric and others
max(n1, n2, ...) / min(...)
Maximum or minimum of the given numbers
abs(n)
Absolute value
ceil(n) / floor(n)
Rounds up / rounds down
coalesce(v1, v2, ...)
First non-null, non-empty value in the list
try(expr, fallback)
Evaluates expr; if it fails, returns fallback (useful for optional attributes)
can(expr)
True if expr does not produce an error (for validation)
Did you get this topic?
Test what you just learned
A module manages 3 subnets with for_each using the keys 'public-a', 'public-b' and 'private-a'. The team decides to remove 'public-b' from the map. What will happen on the next terraform apply?
Put what you learned to the test with original Terraform Associate exam questions.
Start Terraform Associate mock examUnlimited mock exams with the Pro plan
Log in to track your progress.