Terraform Configuration · Topic 3 of 4

Expressions, Functions and Dependencies

Terraform Associate 004: conditionals, for expressions, dynamic blocks, built-in functions, implicit and explicit dependencies, depends_on and lifecycle.

  • 4 min
In a nutshell
  • Conditional syntax is condition ? a : b.
  • [for ...] builds a list, {for ... : k => v} builds a map, and if filters.
  • Dynamic blocks repeat nested blocks; the iterator takes the block label name.

What it is

Expressions compute values instead of hard-coding them. With conditionals, for expressions, dynamic blocks and built-in functions, one configuration can adapt to different inputs.

Dependencies decide the order in which Terraform acts. Most come from references between blocks; depends_on covers the rare case where no reference exists.

Inputsvariables and localsExpressionsconditionals, for, functionsDependency graphimplicit and depends_onOrdered plan

How it works

Conditional expressions

A conditional picks one of two values: condition ? true_value : false_value.

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.env == "prod" ? "m5.large" : "t3.micro"
}

Both results should have the same type. A common pattern is count = var.create ? 1 : 0 to create a resource only when a flag is true.

for expressions

A for expression transforms one collection into another. Square brackets produce a list or tuple, braces produce an object or map.

locals {
  upper_names = [for n in var.names : upper(n)]
  name_to_len = { for n in var.names : n => length(n) }
  prod_only   = [for s in var.servers : s.name if s.env == "prod"]
}

The optional if clause filters elements. A for expression builds a value; it does not create resources by itself.

dynamic blocks

A dynamic block generates repeated nested blocks, such as several ingress rules, from a collection.

resource "aws_security_group" "web" {
  dynamic "ingress" {
    for_each = var.ports
    content {
      from_port   = ingress.value
      to_port     = ingress.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

Inside content, the iterator is named after the block label, here ingress, with .key and .value. The iterator argument renames it. Dynamic blocks generate nested blocks only, never whole resources.

ConstructRepeatsDriven by
countWhole resource or moduleA whole number
for_eachWhole resource or moduleA map or a set of strings
dynamic blockNested blocks inside a resourceAny collection
for expressionValues onlyAny collection

Built-in functions

Terraform has only built-in functions plus functions that providers supply; you cannot write your own. Calls look like name(arg1, arg2).

FunctionResult
upper("web")"WEB"
join("-", ["a", "b"])"a-b"
length(["a", "b", "c"])3
lookup(var.amis, "eu", "none")Value for key, or the default
merge(map1, map2)One map, later keys win
cidrsubnet("10.0.0.0/16", 8, 1)"10.0.1.0/24"
file("init.sh")File content as a string
templatefile("init.tftpl", { port = 80 })Rendered template
toset(["a", "a", "b"])A set of "a" and "b"

Run terraform console to try expressions and functions against the current configuration and state without changing anything.

Implicit and explicit dependencies

A reference such as vpc_id = aws_vpc.main.id creates an implicit dependency. Terraform builds a dependency graph from these references and creates independent resources in parallel.

When a dependency is real but not visible in any argument, add depends_on with a list of references.

resource "aws_instance" "app" {
  ami           = var.ami_id
  instance_type = "t3.micro"
  depends_on    = [aws_iam_role_policy.app_s3]
}

Here the application needs an IAM policy at boot, but no argument refers to it. HashiCorp recommends depends_on only as a last resort, because it can make plans more conservative.

Reference in argumentImplicit dependencyGraph orderHidden needdepends_onExplicit dependencyGraph order

Lifecycle settings

The lifecycle block changes how Terraform replaces or protects a resource.

ArgumentEffect
create_before_destroyCreates the replacement before destroying the old object
prevent_destroyRejects any plan that would destroy the resource
ignore_changesIgnores changes to the listed attributes
replace_triggered_byReplaces the resource when a referenced item changes

How the exam asks about this

  • "Create a bastion host only when var.enable_bastion is true." Use count = var.enable_bastion ? 1 : 0.
  • "An instance needs an S3 policy at boot but references nothing from it. How do you enforce order?" Add depends_on to the instance.
  • "Generate one ingress rule per port in a list." Use a dynamic "ingress" block with for_each.

What to remember for the exam

  • Conditional syntax is condition ? a : b.
  • [for ...] builds a list, {for ... : k => v} builds a map, and if filters.
  • Dynamic blocks repeat nested blocks; the iterator takes the block label name.
  • Only built-in and provider-defined functions exist; there are no user-defined functions.
  • References create implicit dependencies; depends_on adds explicit ones for hidden relationships.
  • terraform console tests expressions interactively.
Watch out

for_each accepts a map or a set of strings, not a list. Convert a list with toset(var.names) before using it.

Common mistakes

Adding depends_on everywhere. If an argument already refers to another resource, the dependency exists. Extra depends_on entries add nothing and can slow plans down.

Using a dynamic block to repeat resources. Dynamic blocks only generate nested blocks. To create several resources, use count or for_each on the resource itself.

Mixing result types in a conditional. A conditional that returns a string on one side and a list on the other causes a type error. Both results must convert to one type.

Check yourself

  • How does Terraform decide the order in which to create resources?

    It builds a dependency graph from references between blocks plus any depends_on entries, then creates independent resources in parallel.

  • What does [for s in var.names : upper(s)] return?

    A list with every name in var.names converted to upper case.

  • Can you define your own function in Terraform configuration?

    No. You can call built-in functions and functions supplied by providers, but the language has no way to define new ones.

Topic 3 of 4 in Terraform Configuration

Test yourself on Terraform Configuration

The full HashiCorp Terraform Associate 004 question bank has 600 questions, with timed practice exams and an explanation for every answer.

This study guide is independent and is not affiliated with, endorsed by, or sponsored by HashiCorp. Vendor names, certification names and exam codes are trademarks of their respective owners and are used for identification only.