Short-Circuit Evaluation in Terraform: A Deep Dive

Short-circuit evaluation is a fundamental concept in programming that can significantly impact how your Terraform configurations behave. Unlike traditional programming languages, Terraform's HashiCorp Configuration Language (HCL) has some unique characteristics when it comes to short-circuit evaluation that every DevOps engineer should understand.
What is Short-Circuit Evaluation?
Short-circuit evaluation is an optimization technique where the second operand of a logical operation is only evaluated if the first operand doesn't determine the result. In most programming languages:
- For
AND
operations: if the first condition isfalse
, the second isn't evaluated - For
OR
operations: if the first condition istrue
, the second isn't evaluated
How Traditional Languages Handle Short-Circuit Evaluation
In languages like JavaScript, Python, or Go, short-circuit evaluation prevents unnecessary computation and potential errors:
// JavaScript example const user = getUser(); if (user && user.password) { // user.password is only checked if user exists console.log('User has password set'); }
# Python example if user is not None and user.password: # user.password only evaluated if user is not None print("User has password set")
The Problem: Terraform's Short-Circuit Evaluation Doesn't Always Work
Here's a real-world example that demonstrates the issue:
variable "user_config" { type = object({ username = string email = string password = optional(string) }) default = null } # ❌ This WILL FAIL with "Attempt to get attribute from null value" locals { needs_random_password = var.user_config != null && var.user_config.password == null }
Why does this fail? Even though we check var.user_config != null
first, Terraform still evaluates the entire expression during the parsing phase and discovers that var.user_config.password
is trying to access an attribute on a null value.
Terraform's Unique Approach
Terraform's HCL has a more complex relationship with short-circuit evaluation due to its declarative nature and the way it processes configurations during different phases (plan, apply, etc.).
The Reality: Limited Short-Circuit Evaluation
Unlike traditional programming languages, Terraform's short-circuit evaluation is limited because:
- Parsing Phase: Terraform parses the entire expression before evaluation
- Type Checking: All attribute accesses are validated regardless of conditions
- Static Analysis: Terraform needs to understand all possible code paths
Solutions: Safe Ways to Handle Null Values
Solution 1: Explicit Boolean Variables
The most straightforward and readable approach is to use explicit boolean variables with validation:
variable "create_user" { description = "Whether to create the user" type = bool default = false } variable "user_config" { description = "User configuration (required when create_user is true)" type = object({ username = string email = string password = optional(string) }) default = null validation { condition = !var.create_user || var.user_config != null error_message = "user_config must be provided when create_user is true." } } resource "random_password" "user_password" { count = var.create_user && var.user_config.password == null ? 1 : 0 length = 16 special = true } resource "aws_iam_user" "example" { count = var.create_user ? 1 : 0 name = var.user_config.username tags = { Email = var.user_config.email } } resource "aws_iam_user_login_profile" "example" { count = var.create_user ? 1 : 0 user = aws_iam_user.example[0].name password = var.user_config.password != null ? var.user_config.password : random_password.user_password[0].result }
Why this works: By separating the boolean flag from the configuration object, we avoid null attribute access entirely. The validation block ensures data integrity.
Note: Variable validation is a powerful tool that should be leveraged whenever possible. It provides early feedback to users, prevents configuration errors, and makes your modules more robust and user-friendly. The validation block runs during terraform plan
and terraform apply
, catching issues before resources are created.
Solution 2: Use try()
Function
For cases where explicit boolean variables aren't preferred:
locals { needs_random_password = try(var.user_config.password == null, false) } resource "random_password" "user_password" { count = local.needs_random_password ? 1 : 0 length = 16 special = true }
Why this works: The try()
function catches any errors during expression evaluation and returns the fallback value (false
) if the expression fails. This prevents the "Attempt to get attribute from null value" error by gracefully handling the case where var.user_config
is null.
Solution 3: Use can()
Function
locals { needs_random_password = can(var.user_config.password) && var.user_config.password == null } resource "random_password" "user_password" { count = local.needs_random_password ? 1 : 0 length = 16 special = true }
Why this works: The can()
function tests whether an expression can be evaluated without errors. It returns true
if the expression is valid and false
if it would cause an error. This allows us to safely check if var.user_config.password
is accessible before evaluating it, effectively implementing our own short-circuit logic.
Solution 4: Nested Conditional Expression
locals { needs_random_password = var.user_config != null ? var.user_config.password == null : false } resource "random_password" "user_password" { count = local.needs_random_password ? 1 : 0 length = 16 special = true }
Why this works: The ternary conditional operator (condition ? true_value : false_value
) provides proper short-circuit behavior in Terraform. The second part of the expression (var.user_config.password == null
) is only evaluated if the first condition (var.user_config != null
) is true, preventing null attribute access errors.
Summary
Short-circuit evaluation in Terraform is not the same as in traditional programming languages. The most effective approaches:
- Use explicit boolean variables with validation for feature flags and optional resources - this is the most robust approach that leverages Terraform's validation capabilities
- Use
try()
function for optional nested attributes and fallback values - Use
can()
function to test expression validity - Use variable validation wherever possible to catch errors early and provide better user experience
- Combine approaches based on your team's preferences and the complexity of your configuration
The explicit boolean variable approach with validation tends to be more readable, maintainable, and provides the best user experience, while function-based approaches can be more concise for simple cases. Choose what works best for your team and use case, but always consider adding validation to improve reliability.