Terraform configuration files are written in HCL, and the smallest units are the block, the attribute (argument), and the expression. Once you grasp how these three differ and relate to one another, you can read and write any resource without hesitation.
This article follows the terminology and behavior in the official docs, takes Associate exam patterns into account, and focuses on concrete examples and checkpoints.
A Terraform .tf file is a collection of blocks. Each block is made up of a type, zero or more optional labels, and a body ({ ... }) that contains attributes (key = value) and nested blocks. What you can write on the value side is called an expression.
Comments use # or //, and multi-line comments use /* */. Identifiers use letters, digits, and underscores, and may not start with a digit. No special markers are needed at the start or end of a file, and multiple files are combined per directory (the .tf files in the same directory are logically merged).
HCL syntax relationships (file -> block -> attribute / nested block -> expression)
Minimal example: a block containing attributes and a nested block
resource "random_pet" "name" {
length = 2 # 属性(value は number リテラル)
prefix = var.project # 属性(value は式:変数参照)
separator = "-" # 属性(value は string リテラル)
}
output "pet_name" {
value = random_pet.name.id # 式:リソース属性参照
}
A block is the syntactic unit of HCL. The form is block_type "label1" "label2" { ... } with zero or more labels. The classic case is resource, which takes two labels (type and name). Nested blocks also exist (for example lifecycle, provisioner, and dynamic inside resource).
In official Terraform terminology, key = value pairs inside a block body are called arguments, but they are also commonly written as attributes. Knowing they are synonymous keeps you from being thrown off by the term argument on the exam or in the docs.
| Concept | Role | Example / Syntax | Pattern |
|---|---|---|---|
| Block | Defines structure (type, labels, body) | resource "aws_s3_bucket" "this" { ... } | Contains nested blocks and attributes |
| Attribute (argument) | Specifies a property or value of the block | bucket = "example" | key = value (value is an expression) |
| Expression | Computes or composes a value | var.name, aws_vpc.main.id, join(",", list) | Literal / reference / function / for / conditional |
Examples of block types and labels
# ラベルなし
terraform {
required_version = ">= 1.5.0"
}
# 1 ラベル
variable "project" {
type = string
description = "Project name"
}
# 2 ラベル(type と name)
resource "random_pet" "this" {
length = 2
}
# 入れ子ブロック(lifecycle)
resource "null_resource" "hook" {
triggers = { t = timestamp() }
lifecycle {
prevent_destroy = true
}
}
Attributes are written in a block body as key = value. The value is always an expression and can be a literal (string/number/bool), a collection (list/set/map), a structural value (object/tuple), a reference, a function call, a for expression, or a conditional.
Strings use double quotes, and multi-line strings can use heredocs (<<EOT ... EOT). true and false should be written as booleans without quotes. Implicit type conversion sometimes works, but you should declare type explicitly on variable blocks and normalize values with tonumber and similar functions when needed.
Examples of attribute types and forms
variable "env" { type = string }
locals {
enabled = true # bool
retries = 3 # number
owners = ["ops", "dev"] # list(string)
tags = { env = var.env } # map(string)
config = { retries = 3, debug = false } # object
message = <<-EOT
Hello ${var.env}
EOT
}
output "tags" {
value = merge(local.tags, { app = "web" }) # 関数呼び出しも式
}
Expressions are the syntax for producing values. The main forms are references (var.x, local.y, resource.attr), functions (merge, join, coalesce, and so on), the conditional operator (cond ? x : y), for expressions (list/map comprehensions), and the splat operator ([*]).
In 0.12+, ${} is unnecessary in most places. When embedding an expression inside a string, use interpolation like "name-${var.env}". Values that are not determined at plan time (unknown values) become "known after apply", which constrains how they can be used in functions and comparisons.
Concrete expression examples (references, functions, conditionals, for)
variable "names" { type = list(string) }
locals {
upper_names = [for n in var.names : upper(n)]
name_map = { for idx, n in var.names : idx => n }
pick = length(var.names) > 0 ? var.names[0] : "default"
tags = merge({ env = "dev" }, { app = "api" })
}
output "first_or_default" {
value = local.pick
}
Use for_each or count to create multiple resources of the same type. for_each is better when you want to manage by key or keep downstream references stable, while count is fine when index-based identity is enough.
Use depends_on for explicit dependencies, but most dependencies are resolved automatically through attribute references. Input precedence is -var / -var-file > *.auto.tfvars > terraform.tfvars > the TF_VAR_* environment variables > the variable's default (earlier wins).
Examples of for_each, count, and depends_on
variable "subnets" { type = map(string) } # name => cidr
resource "null_resource" "by_each" {
for_each = var.subnets
triggers = { name = each.key, cidr = each.value }
}
resource "null_resource" "by_count" {
count = 2
triggers = { idx = count.index }
}
resource "null_resource" "needs_each" {
depends_on = [null_resource.by_each]
triggers = { ready = true }
}
The basic rule is to express types directly: do not write true/false as strings, do not quote numbers. The 0.12+ convention is to use ${} interpolation only inside strings and avoid it where it is unnecessary.
Do not mix up the reference prefixes for resource and data, the evaluation timing of output and locals, or how unknown values (not yet determined at plan time) are handled. The distinction between blocks and attributes (for example lifecycle is a block while tags is a map attribute) is also a frequent exam topic.
Common mistakes and the correct way to write them
# 誤:文字列化された bool
# enabled = "true"
# 正:
enabled = true
# 誤:リソース全体参照
# value = aws_subnet.app
# 正:
value = aws_subnet.app.id
# 誤:不要な補間(0.12+)
# name = "${var.project}"
# 正:
name = var.project
Associate
問題 1
Which HCL element does the following line most accurately represent? availability_zones = length(data.aws_availability_zones.current.names)
正解: A
It defines an attribute (argument) in the key = value form, and the value side is an expression. The expression references a data source (data.aws_availability_zones.current.names) and computes its length with the length function.
Do the terms argument and attribute in the Terraform docs mean the same thing?
They are effectively synonymous. The Terraform spec and docs call the key = value pairs in a block body arguments, while many general explanations call them attributes. On the exam, do not get thrown off when you see the word argument.
Is ${} interpolation still required?
Since 0.12, expressions can be written directly in most places, so it is no longer needed. Use interpolation like "name-${var.env}" when embedding a variable or reference inside a string. When the entire right-hand side of an attribute is an expression, write var.env or local.tags without ${}.
What is the difference between null and an empty string ("")?
null means "no value" and providers or resources treat it as unset or as triggering a default. An empty string is a length-0 string and is an explicit value. Guard with coalesce or try so that conditional expressions or merge results do not unintentionally produce empty strings.
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...