linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/kb/Terraform basics/tf-functions-collection

kb/core ── Terraform basics ── beginner

HCL collection functions: length, lookup, merge, concat, flatten

HCL has functions for list/set/map: length (size), lookup (by key with a default), merge (combine maps), concat (join lists), flatten (unnest), keys/values. The basic toolkit for transformations.

view as markdown

length, the size of a collection

Works on list, set, map, and string:

hcl
length(["a", "b", "c"])              # 3
length(toset(["a", "b", "b"]))       # 2 (duplicate removed)
length({a = 1, b = 2})               # 2
length("hello")                       # 5

Useful for conditions and validation:

hcl
validation {
  condition     = length(var.azs) >= 2
  error_message = "At least 2 availability zones required."
}

lookup, a value by key with a default

hcl
lookup({a = 1, b = 2}, "a")           # 1
lookup({a = 1, b = 2}, "missing")     # error: key not found
lookup({a = 1, b = 2}, "missing", 0)  # 0 (third arg = default)

The third argument is the fallback used when the key is not found. Without it, lookup fails. In practice you almost always pass a default.

The modern shortcut is map["key"]:

hcl
var.tags["Environment"]          # like lookup but without a default; fails if the key is absent
try(var.tags["Environment"], "")  # with a default via try

merge, combining maps

Several maps into one. Later maps override earlier ones:

hcl
merge(
  { Project = "demo", Owner = "team" },
  { Owner = "platform", Env = "prod" }
)
# {
#   Project = "demo"
#   Owner   = "platform"   # overridden
#   Env     = "prod"
# }

The main case is common_tags plus specific tags:

hcl
locals {
  common_tags = { Project = var.project, ManagedBy = "terraform" }
}
resource "aws_s3_bucket" "logs" {
  bucket = "logs-bucket"
  tags = merge(local.common_tags, {
    Purpose = "logs"
  })
}

concat, joining lists

hcl
concat(["a", "b"], ["c", "d"])           # ["a", "b", "c", "d"]
concat(var.base_cidrs, var.extra_cidrs)  # join two CIDR lists

Order is preserved. Duplicates are not removed (wrap the result in distinct if you need that).

flatten, unnest nested lists

Often the result of a for expression or several concat calls is an array of arrays. flatten turns it into a flat list:

hcl
flatten([["a", "b"], ["c"], [], ["d", "e"]])
# ["a", "b", "c", "d", "e"]

A typical case: collect all IPs of all instances across all groups:

hcl
locals {
  all_private_ips = flatten([
    for asg in aws_autoscaling_group.tiers : asg.target_group_arns
  ])
}

It unnests only one level. If there is deeper nesting inside, use flatten(flatten(...)).

keys, values, the parts of a map

hcl
keys({a = 1, b = 2, c = 3})       # ["a", "b", "c"]  (sorted by key)
values({a = 1, b = 2, c = 3})     # [1, 2, 3]         (same order as keys)

keys always returns a sorted list, which is handy for stable results in plan.

contains, checking for membership

hcl
contains(["dev", "staging", "prod"], var.env)
# true / false

Its most common role is in validation:

hcl
validation {
  condition     = contains(["dev", "staging", "prod"], var.env)
  error_message = "env must be dev, staging, or prod."
}

There is no contains for a map. Use contains(keys(map), "k") or try(map["k"], null) != null.

distinct, remove duplicates

hcl
distinct(["a", "b", "a", "c", "b"])   # ["a", "b", "c"]

Order is preserved (first occurrence). The alternative is toset(...), but toset loses the order.

element and slice

hcl
element(["a", "b", "c"], 1)       # "b"
element(["a", "b", "c"], 5)       # "c", wraps around: 5 % 3 = 2
slice(["a", "b", "c", "d"], 1, 3) # ["b", "c"], [start, end)

element wraps around with the modulo operator, which is its quirk. If you want a hard error when the index goes out of bounds, use list[index].

zipmap, build a map from two lists

hcl
zipmap(["a", "b", "c"], [1, 2, 3])
# {a = 1, b = 2, c = 3}

The lengths must match, otherwise you get an error.

for expressions, more powerful than any function

hcl
# list → list
[for s in ["dev", "prod"] : upper(s)]
# ["DEV", "PROD"]
# list → map
{ for s in ["dev", "prod"] : s => "bucket-${s}" }
# {dev = "bucket-dev", prod = "bucket-prod"}
# with a filter
[for s in var.envs : s if s != "test"]

A for expression covers most transformations. Many of the built-in functions (keys, values, sometimes merge) can be rewritten with for.

Gotchas

  • merge is flat. If the values are themselves maps, they get overwritten, not merged. For a deep merge, use several merge calls or do it by hand with a for expression.

  • concat does not deduplicate. If you need to combine and drop duplicates, use distinct(concat(a, b)).

  • lookup without a default blows up. Always pass the third argument or use try().

  • flatten goes one level deep. For double nesting, use flatten(flatten(arr)).

  • keys sorts, which is good for stability. If you need the original order of a map's keys, that is a problem in itself, because an HCL map is unordered by nature. Use a list of objects instead.

  • element wraps around, but list[index] does not. element(arr, 999) gives a result by modulo. arr[999] fails. For a safe lookup, use try(arr[index], default).

  • contains is strictly typed. contains([1, 2, 3], "1") fails, because "1" is a string and 1 is a number. Cast the types explicitly.

  • for with a map requires unique keys. In { for s in arr : key_func(s) => ... }, if key_func produces the same key for two elements, it fails. Fix it with for ... if cond or by changing the key.

§ команды

bash
terraform console

The best way to learn the collection functions. Type `merge({a=1},{b=2})` and you see the result right away.

bash
echo 'flatten([[1,2],[3,4]])' | terraform console

A quick one-line check from the shell.

bash
terraform validate

Type checking in expressions: it fails without reaching the cloud if concat mixed incompatible types.

§ см. также

  • tf-functions-stringHCL string functions: format, join, replace, lower, and moreHCL string functions: format (like printf), join/split (lists to strings and back), replace (regex or text), lower/upper/title (case), trimspace and the trim* family. All pure, no side effects, handy in locals.
  • tf-interpolation${...}: substituting values into strings${expression} inside a string substitutes the value of that expression. ${var.env}, ${aws_s3_bucket.demo.id}. When the expression is the entire value of an argument (no surrounding string), modern HCL lets you write it without ${...}.
  • tf-localslocals: computed internal nameslocals is a block with names visible only inside HCL (not input, not output). Useful for DRY: compute a common prefix or tag set once, then use it everywhere via local.x. Do not confuse with variable (input) and output (output).
  • hcl-typesData types in HCL: string, number, list, map, objectHCL supports primitives (string, number, bool) and complex types: list, set, map, tuple, object. This article covers the syntax of each one and the difference between the look-alikes (list vs tuple, map vs object).
  • tf-count-for-eachcount and for_each: many resources from one blockcount creates N identical resources by index 0..N-1. for_each creates resources keyed from a set or map. Rule of thumb: count for identical copies, for_each when each one has its own settings. When in doubt, use for_each.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies