Terraform's built-in functions are the key to keeping declarations simple while still handling variability. length, lookup, and templatefile in particular show up frequently even at the Associate level.
This article walks through their behavior based on the official spec and consolidates the points that tend to appear on the exam alongside real-world best practices.
Terraform functions are evaluated as expressions and resolved at plan time wherever possible. When unknown values are involved (for example, from external data sources), functions that cannot defer evaluation may raise errors. Functions are treated as pure functions with no side effects.
Types matter. length works on strings, lists, sets, and maps (including objects), but not on numbers or booleans. lookup retrieves a value from a map and only returns the default when the key is absent (the fact that it returns null when the key exists but the value is null is a popular exam trap). templatefile takes a file path and a variable map and renders the template.
Conceptual flow of evaluation involving functions
Inputs (variables, data) ---> Expressions (functions)
| \
| +--> locals
v
Resource arguments ----> Plan ----> Apply
Files: templatefile(path.module/...) --reads--> Template --renders--> String for argumentsFunction basics: a minimal example of types and evaluation
variable "names" { type = list(string) default = ["app", "db"] }
variable "tags" { type = map(string) default = {} }
locals {
name_count = length(var.names) # 2
maybe_owner = try(var.tags["owner"], null) # null if absent
# path.module is the current module folder
}
length is the most common foundational function. It returns the length of a string or the number of elements in a collection (list/set/map/tuple/object). On the Associate exam it typically shows up combined with count or for_each.
Watch out for null and unknown values. length(null) raises an invalid-argument error. If unknown values may be involved, guard with coalesce against a same-typed empty value, or pair it with try/can.
Practical patterns with length
variable "subnets" { type = list(string) default = [] }
variable "tags" { type = map(string) default = null }
locals {
subnet_count = length(var.subnets) # 0 or more
safe_tag_count = length(coalesce(var.tags, tomap({}))) # replace null with empty map
non_empty_name = length(trimspace(var.name)) > 0 # string emptiness check
}
resource "null_resource" "per_subnet" {
count = length(var.subnets)
}
# When the value may be unknown
locals {
# Safely assign with try (use tomap({}) as the second arg to keep types aligned)
maybe_tags = try(var.tags, tomap({}))
tag_count = length(maybe_tags)
}
The standard safe way to fetch a value from a map is lookup(map, key, default). It only returns the default when the key is absent; if the key exists and its value is null, it returns null. This behavior is a popular exam topic.
Direct subscript map["key"] errors out on a missing key. If the key may be missing or unknown, use try(map["key"], default) or lookup. To fall back when the value is an empty string or empty collection, combine with trimspace, length, and a conditional.
| Approach | Missing key | When value is null | Main use case |
|---|---|---|---|
| lookup(m, "k", "def") | Returns def | Returns null (not def) | Use the default only when the key is missing |
| m["k"] | Error (Invalid index) | null | When the key is guaranteed to exist |
| try(m["k"], "def") | Returns def | Returns def | Force the default for missing keys or null values |
Safe retrieval examples with lookup / subscript / try
variable "meta" { type = map(string) default = {} }
locals {
# Use "dev" only on missing key; keep null as null
env_lookup = lookup(var.meta, "environment", "dev")
# Force "dev" even on missing key or null
env_force_default = try(var.meta["environment"], "dev")
# Key existence check
has_env = contains(keys(var.meta), "environment")
# Fall back when empty or whitespace
owner_raw = try(var.meta["owner"], null)
owner = (owner_raw != null && trimspace(owner_raw) != "") ? trimspace(owner_raw) : "unknown"
}
templatefile(path, vars) reads a file and interpolates from the vars map. Building the path relative to path.module keeps it safe when the module is distributed. Inside the template you reference values with the ${var} form and can also use control directives like %{ if }.
When passing complex structures into a template, run them through jsonencode or yamlencode first; that plays nicely with shell scripts and cloud-init.
templatefile example (externalizing user data)
# main.tf
variable "cluster_name" { type = string }
variable "tags" { type = map(string) default = {} }
locals {
user_data = templatefile("${path.module}/user_data.sh.tmpl", {
cluster_name = var.cluster_name,
tags_json = jsonencode(var.tags)
})
}
# user_data.sh.tmpl (example template contents)
#cloud-config
runcmd:
- echo "Cluster: ${cluster_name}" > /etc/motd
- echo 'Tags: ${tags_json}' >> /etc/motd
%{ if length(trimspace(cluster_name)) == 0 }
- echo "WARN: cluster name is empty" >> /var/log/setup.log
%{ endif }
In practice you compose functions to build robust expressions. merge merges maps, coalesce returns the first non-null value, and try returns the value of the first expression that succeeds. join/split, compact, distinct, and toset are staples for shaping collections.
The trick is to distinguish unknown, missing, and empty. Choose between null and an empty string or empty collection based on intent, and convert explicitly when necessary.
Combining helper functions
variable "base_tags" { type = map(string) default = { app = "web" } }
variable "extra_tags" { type = map(string) default = null }
variable "raw_subnets" { type = list(string) default = ["subnet-a", "", "subnet-b", "subnet-a"] }
locals {
tags = merge(var.base_tags, coalesce(var.extra_tags, tomap({})))
# Drop empty strings -> dedupe -> turn into a set
subnets_clean = toset(distinct(compact(var.raw_subnets)))
# Build a CSV
subnet_csv = join(",", sort(tolist(subnets_clean)))
# Use try for fallible retrieval
env = try(var.extra_tags["environment"], "dev")
}
The exam targets edge cases (null, missing, empty), evaluation timing (plan vs. apply), and the choice between path variables. Knowing the pitfalls keeps you from leaving easy points on the table.
In real work, the playbook is: design expressions to be safe in the face of unknowns, push templating into templatefile, and build tags or labels robustly with merge and lookup/try.
Fixing common pitfalls
# Bad: errors on missing key
# local.env = var.tags["environment"]
# Good: tolerates missing key and null
locals {
env = try(var.tags["environment"], "dev")
}
# Bad: passing null straight into length
# local.c = length(var.tags)
# Good: guard with the same type
locals {
c = length(coalesce(var.tags, tomap({})))
}
Associate
問題 1
You want to fetch the key "team" from the map var.tags, return "platform" only when the key is absent, and leave the result as null when the key exists but its value is null. Which expression best satisfies these requirements?
正解: A
lookup(map, key, default) returns the default only when the key is missing and returns null when the key exists with a null value. B errors on a missing key, C switches to the default when the value is null (different from the requirement), and D has its condition reversed and does not meet the requirement.
Can I reference Terraform variables like var.x directly inside a templatefile template?
No. Only the keys of the map you pass to templatefile are visible inside the template. Pass every value you need explicitly via the second argument.
How does length handle multibyte characters when measuring string length?
It counts characters (not bytes), so length("あ") returns 1.
When should I use lookup versus try?
Use lookup when you only want to fall back to the default on missing keys. Use try when you want any exceptional situation (missing key, null, etc.) to collapse to the default. In practice pick based on requirements and keep null vs. empty clearly distinguished.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...