Fix Terraform Error - Invalid Template Interpolation
Fix the Terraform invalid template interpolation error when embedding complex types in strings. Covers jsonencode, join, format, and type conversion patterns.
Troubleshooting
Fix the Terraform inconsistent conditional result types error. Covers type conversion, null handling, tostring, tolist, and splitting complex conditionals.
The true and false branches of your ternary expression return different types (e.g., string vs number, list vs null). Convert both sides to the same type using tostring(), tolist(), or toset(), or use null which is compatible with any type.
When running terraform plan or terraform validate:
Error: Inconsistent conditional result types
on main.tf line 5, in local:
5: value = var.enabled ? var.instance_count : "none"
The true and false result expressions must have consistent types.
The 'true' value includes object, and the 'false' value is string.Error: Inconsistent conditional result types
The true result is list of string, but the false result is tuple.Terraform is statically typed. A conditional expression (condition ? true_val : false_val) must return the same type from both branches. Terraform cannot infer a common type when they differ.
# String vs Number
var.enabled ? 1 : "disabled"
# List vs Empty tuple
var.enabled ? ["a", "b"] : []
# Object vs null
var.config != null ? var.config : {}
# String vs Number in interpolation
"Count: ${var.enabled ? var.count : "none"}"# BAD — number vs string
locals {
port = var.use_https ? 443 : "default"
}
# GOOD — both strings
locals {
port = var.use_https ? "443" : "default"
}
# GOOD — both numbers
locals {
port = var.use_https ? 443 : 80
}
# Convert explicitly
locals {
port_string = var.use_https ? tostring(443) : "default"
}# tostring() — convert to string
locals {
display = var.count > 0 ? tostring(var.count) : "none"
}
# tonumber() — convert to number
locals {
timeout = var.custom_timeout != "" ? tonumber(var.custom_timeout) : 30
}
# tolist() — convert to list
locals {
subnets = var.use_private ? tolist(var.private_subnets) : tolist(var.public_subnets)
}
# toset() — convert to set
locals {
users = var.include_admins ? toset(concat(var.users, var.admins)) : toset(var.users)
}null is compatible with any type, making it the safest "empty" value:
# BAD — string vs empty map
locals {
config = var.enabled ? var.config_map : {}
}
# GOOD — null is type-compatible with any value
locals {
config = var.enabled ? var.config_map : null
}
# Use null for optional resource arguments
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
key_name = var.enable_ssh ? var.key_pair_name : null # null = omit the argument
subnet_id = var.private ? var.private_subnet : var.public_subnet
}Empty [] is a tuple, not a list. Convert explicitly:
# BAD — list(string) vs tuple
locals {
cidrs = var.restrict_access ? var.allowed_cidrs : []
}
# GOOD — explicit list type
locals {
cidrs = var.restrict_access ? var.allowed_cidrs : tolist([])
}
# BETTER — use coalesce pattern
locals {
cidrs = var.restrict_access ? var.allowed_cidrs : ["0.0.0.0/0"]
}# BAD — complex nested conditional
resource "aws_instance" "web" {
tags = var.environment == "prod" ? {
Name = "prod-web"
Backup = "daily"
} : var.environment == "staging" ? {
Name = "staging-web"
} : {
Name = "dev-web"
}
}
# GOOD — break into locals with consistent types
locals {
instance_tags = {
prod = {
Name = "prod-web"
Backup = "daily"
}
staging = {
Name = "staging-web"
Backup = "none" # Same keys as prod
}
dev = {
Name = "dev-web"
Backup = "none" # Same keys as prod
}
}
}
resource "aws_instance" "web" {
tags = local.instance_tags[var.environment]
}# Instead of a conditional that might have type issues
locals {
db_host = try(var.external_db_host, aws_db_instance.main[0].endpoint)
}
# Safely access nested optional attributes
locals {
alarm_email = try(var.monitoring.alarm_email, "ops@example.com")
}| Expression | Problem | Fix |
|---|---|---|
cond ? 1 : "none" | number vs string | cond ? "1" : "none" |
cond ? list : [] | list vs tuple | cond ? list : tolist([]) |
cond ? map : {} | map vs object | cond ? map : null |
cond ? "value" : null | Usually works | ✅ null is type-compatible |
cond ? 0 : null | Usually works | ✅ null is type-compatible |
cond ? set : list | set vs list | cond ? tolist(set) : list |
null instead of an empty value?[] causing a tuple vs list mismatch?locals block make types clearer?type = string prevents mixed types from sneaking innull as the "empty" value — it's compatible with any typetolist([]) not []terraform validate — catches type errors without hitting the cloud APITerraform requires both branches of a conditional to return the same type. Use type conversion functions (tostring, tolist, toset) to align types, use null for optional values, and break complex conditionals into locals with explicit type structures. Running terraform validate catches these errors before plan.
Fix the Terraform invalid template interpolation error when embedding complex types in strings. Covers jsonencode, join, format, and type conversion patterns.
Fix the Terraform invalid function argument error. Covers type mismatches for join, lookup, cidrsubnet, file, and other built-in functions with examples.
Fix the Terraform 'Backend configuration changed' error. Migrate state between backends (local to S3, S3 to S3), resolve lock conflicts
Fix Terraform provider version conflicts between modules, lock files, and constraint mismatches. Resolve dependency lock errors, upgrade providers safely