A Terraform conditional expression alone cannot suppress resource creation. To toggle whether a resource exists, use count or for_each.
For attribute-level on/off, returning null from a conditional expression is the idiomatic approach. Behavior under plan-time unknown values and the type-compatibility rules are also frequent exam topics.
A Terraform conditional expression has the form condition ? true_val : false_val. If the condition is true, true_val is evaluated; if false, false_val is evaluated. The unselected branch is not evaluated, but the two branches must still have compatible types.
From an exam perspective, the two points that come up the most are type compatibility and the fact that an unselected branch is allowed to reference things that do not exist. If the condition is unknown at plan time, the result may also be unknown. Passing an unknown value into the condition for count/for_each is not allowed — it must be decidable at plan time.
Example: type compatibility in conditional expressions
variable "env" { type = string }
locals {
is_prod = var.env == "prod"
# OK: number and number
replicas = local.is_prod ? 3 : 1
# OK: list and list (both list(string))
cidrs = local.is_prod ? ["10.0.0.0/8"] : ["192.168.0.0/16"]
# NG example (type mismatch): number and string → plan error
# bad = local.is_prod ? 1 : "1"
}You cannot wrap a Terraform resource block itself in a conditional expression. To toggle whether a resource is created, use the count or for_each meta-arguments. Use count to toggle a single resource on/off, and for_each when you need to create multiple resources with stable keys.
The expression for count/for_each must be decidable at plan time. For example, using a value that depends on a data source and is only known at apply time will cause an error.
| Mechanism | Primary use | Caveats / downsides | Typical code |
|---|---|---|---|
| Conditional only | Switching values or expressions (attributes/variables) | Cannot toggle the existence of a resource | var.enabled ? "on" : "off" |
| count | Toggle a single resource on/off | Index references need to be guarded | count = var.create ? 1 : 0 |
| for_each | Multiple instances with stable keys | Changing keys cause destroy/create cycles | for_each = var.create ? {"main"=true} : {} |
| Return null | Disabling an attribute (treated as unspecified) | Blocks need dynamic to toggle | tags = var.add_tags ? local.tags : null |
Conditional creation flow with count
var.create
|
v
condition ? 1 : 0 ---> count = 1 ----> resource.example[0] is created
\
\-> count = 0 ----> 0 instances (nothing created)Toggling resources with count/for_each and guarding references safely
variable "create" { type = bool }
# A single on/off is best handled with count
resource "null_resource" "one" {
count = var.create ? 1 : 0
triggers = {
purpose = "demo"
}
}
# Guard references with a conditional or try
output "one_id" {
value = var.create ? null_resource.one[0].id : null
}
output "one_id_try" {
value = try(null_resource.one[0].id, null)
}
# When stable keys matter, prefer for_each
resource "null_resource" "named" {
for_each = var.create ? { main = true } : {}
triggers = {
name = each.key
}
}
output "named_keys" {
value = keys(null_resource.named)
}For most arguments, passing null is equivalent to leaving the argument unspecified. When you want to conditionally omit something like tags or user_data, returning null from a conditional expression is the safe approach.
To toggle a block (such as versioning) on/off, combine a dynamic block with a conditional / for_each expression.
Example: conditionally omitting arguments and blocks
# Omit an attribute by returning null
variable "attach_user_data" { type = bool }
resource "aws_instance" "web" {
ami = "ami-xxxxxxxx"
instance_type = "t3.micro"
user_data = var.attach_user_data ? file("${path.module}/init.sh") : null
}
# Toggle a block on/off with dynamic
variable "enable_versioning" { type = bool }
resource "aws_s3_bucket" "b" {
bucket = "example-bucket-12345"
dynamic "versioning" {
for_each = var.enable_versioning ? [1] : []
content {
enabled = true
}
}
}
Because for_each manages resources by key, when you filter the set with a conditional expression you must design the keys to be stable. Changing keys triggers destroy/create.
Subset creation driven by an enabled flag fits naturally into a for-expression with an if clause. To completely disable everything, wrap it in a conditional that returns an empty map {}.
Subset creation with an enabled flag plus a master switch
variable "manage_users" { type = bool }
variable "users" {
type = map(object({ enabled = bool, email = string }))
}
locals {
# Pick only the enabled ones
enabled_users = { for k, v in var.users : k => v if v.enabled }
}
resource "example_user" "this" {
# Master switch: empty map => 0 instances
for_each = var.manage_users ? local.enabled_users : {}
name = each.key
email = each.value.email
}
A conditional expression evaluates only the selected branch. So if a resource is referenced only from the true branch and is not defined when the condition is false, that is not an error. On the other hand, the count/for_each expression must be known at plan time — an unknown value here causes an error.
Guard optional resource references with a conditional or try(). try() evaluates its arguments in order and returns the first one that does not error.
Safe references with conditionals and try()
variable "create" { type = bool }
resource "null_resource" "maybe" {
count = var.create ? 1 : 0
}
# Guard [0] with a conditional
output "maybe_id" {
value = var.create ? null_resource.maybe[0].id : null
}
# Use try() to attempt in order (safe even when count=0)
output "maybe_id_try" {
value = try(null_resource.maybe[0].id, null)
}
Use conditionals to switch values, count/for_each to control resource presence and count, and null to leave attributes unspecified. Keeping these roles separate makes plans readable and safe.
On the exam, lock in three points: you cannot wrap a resource block in a conditional, count/for_each must be known at plan time, and the unselected branch is not evaluated.
Minimal pattern for toggling a module
variable "create_subnet" { type = bool }
module "subnet" {
source = "./modules/subnet"
count = var.create_subnet ? 1 : 0
# Reference module outputs safely
}
output "subnet_id" {
value = try(module.subnet[0].id, null)
}
Associate
問題 1
Which is the most appropriate way to skip creating an S3 bucket when var.create_bucket is false, while still referencing the bucket ID safely from an output?
正解: A
Control the presence of a resource with count/for_each. Guard references with a conditional or try(). You cannot wrap a resource block itself in a conditional, and lifecycle does not control whether the resource is created.
Does a conditional expression evaluate both branches? What happens if the unselected branch references a resource that does not exist?
Only the selected branch is evaluated. Because the unselected branch is not evaluated, referencing a resource whose count is 0 there will not cause an error. However, the two branches must still have compatible types.
Can I drive count/for_each with a value from a data source that is unknown at plan time?
No. count/for_each requires values that are known at plan time. Passing an unknown value triggers a plan-time error. Use variables or locals whose values are decidable at plan time.
What happens to an attribute when a conditional expression returns null?
For most arguments, null is treated the same as if the argument were not specified at all, and the provider will not send that attribute. To toggle whole blocks on/off, combine dynamic blocks with for_each.
Practice with certification-focused question sets
無料で問題を解いてみるNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...