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-conditional-expression

kb/core ── Terraform basics ── beginner

Conditionals and null safety: ?:, try, can, coalesce

The ternary operator a ? b : c is a plain if/else. try(expr, fallback) evaluates an expression or substitutes a backup. can(expr) returns true/false. coalesce(...) returns the first non-null value. They all keep your config from crashing.

view as markdown

The ternary operator ?:

The basic conditional:

hcl
condition ? value_if_true : value_if_false

Examples:

hcl
variable "env" { default = "dev" }
resource "aws_db_instance" "main" {
  instance_class    = var.env == "prod" ? "db.m5.large" : "db.t3.micro"
  multi_az          = var.env == "prod"
  backup_retention  = var.env == "prod" ? 30 : 1
  skip_final_snapshot = var.env != "prod"
}

Both branches must be the same type. You cannot write var.x ? "string" : 42; it fails with "inconsistent conditional result types".

Nested ternaries are unreadable; use locals instead

hcl
# Bad
size = var.env == "prod" ? "large" : var.env == "staging" ? "medium" : "small"
# Better
locals {
  size_by_env = {
    prod    = "large"
    staging = "medium"
    dev     = "small"
  }
  instance_size = local.size_by_env[var.env]
}

Once you have more than two options, a map reads more easily than a chain of ? : ?.

try(expr, fallback): try it or move on

HCL often breaks on "the value is missing". For example, reading a key that is not in a map:

hcl
variable "config" {
  type = map(string)
  default = {
    env = "dev"
  }
}
# Fails if config has no region key
region = var.config["region"]

try() solves this: it tries to evaluate the expression, and if that fails, it takes the fallback:

hcl
region = try(var.config["region"], "us-east-1")
# The key's value if it exists, otherwise "us-east-1".

You can pass several fallbacks:

hcl
region = try(
  var.config["region"],         # 1st attempt
  var.default_config["region"], # 2nd
  "us-east-1"                   # last
)

The first one that evaluates successfully wins.

can(expr): a "will this work?" check

It returns true/false, with no value. This is useful in conditions:

hcl
validation {
  condition     = can(regex("^[a-z][a-z0-9-]{2,62}$", var.bucket_name))
  error_message = "Bucket name must start with a letter, 3-63 chars, lowercase only."
}

regex() fails when it finds no match. can(regex(...)) is true when it matches and false when it fails. That is the right pattern for validation.

Another example:

hcl
locals {
  has_custom_region = can(var.config["region"])
}

coalesce(...): the first non-null value

It takes the first value that is not null:

hcl
region = coalesce(var.custom_region, var.default_region, "us-east-1")

If var.custom_region is null, it checks the next one, and so on. If they are all null, it fails.

This looks like try(), but the difference is:

  • coalesce() looks at null.
  • try() looks at an evaluation error.

If you want "not null and not an empty string", coalesce() skips null on its own but keeps an empty string. Use compact() for lists, or check explicitly.

lookup(map, key, default): a map with a default

The older style of map access with a fallback:

hcl
tags = {
  Env = "prod"
}
env = lookup(tags, "Env", "unknown")        # "prod"
team = lookup(tags, "Team", "unassigned")   # "unassigned", key is missing

Equivalent:

hcl
env = try(tags["Env"], "unknown")

Both forms are valid. try() is more modern and more general.

Pitfalls

  • A ternary with mixed types is an error. var.x ? 42 : "fallback" will not pass. Both branches, one type.

  • try() can mask real errors. If you wrap everything in try(), you will miss a real bug in the expression. Use it surgically.

  • can() masks too. If any error reaches can(...), it returns false, even when the cause is a regex() with a broken pattern. Be careful.

  • coalesce() and empty strings. coalesce("", "fallback") returns "", not the fallback. "" is not null. When you need "not empty", write it out: var.x != "" ? var.x : "fallback".

  • Conditionals do not make if/else blocks. In HCL you cannot "create a resource only if". For that, use count = var.create ? 1 : 0 (creates 0 or 1 resource). The same applies to for_each: for_each = var.create ? toset(["one"]) : toset([]).

  • ?: inside a string, no wrapper needed. bucket = "name-${var.env == "prod" ? "p" : "d"}" is valid. Inside ${...} you can use full expressions.

§ команды

bash
terraform console

The best place to check tricky conditions. Type an expression and you see the result.

bash
echo 'try(var.x["key"], "fallback")' | terraform console

You can run try() straight from the terminal through stdin.

§ см. также

  • hcl-syntaxHCL: the language you write Terraform inHCL (HashiCorp Configuration Language) is the language you use to describe the desired state of your infrastructure. It looks like JSON, but it is easier to read: you can write comments, variables, and loops.
  • 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).
  • tf-functions-collectionHCL collection functions: length, lookup, merge, concat, flattenHCL 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.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies