HCL Syntax Basics
HashiCorp Configuration Language — HCL — is the language every Terraform configuration is written in. It is built around blocks, arguments, and expressions, and it is designed to be read and reviewed rather than fully programmed. You describe resources and the relationships between them, and Terraform figures out the order. Learning to read a block's anatomy makes every provider's documentation immediately usable.
HCL is not a general-purpose programming language, and trying to use it as one is the first thing that trips people up. There are no if statements or for loops as control flow; the language is expression-based. Once you accept that, its small surface becomes an advantage — configurations stay declarative and reviewable.
Blocks and Arguments
Everything in HCL is a block or an argument. A block has a type, optional labels, and a body in braces: resource "aws_instance" "web" { ... } is a block of type resource with two labels — the resource type and your chosen name. Inside, arguments are simple key = value pairs. The snippet below shows the anatomy: the block type, its two labels, and its arguments.
# block type ── resource type ─── local name resource "aws_instance" "web" { ami = "ami-0abc123" # arguments are key = value instance_type = "t3.micro" tags = { Name = "web-server" } }
Types and Values
HCL values are strings, numbers, bools, and the collection types: lists (ordered), maps (key-value), and objects (structured). Strings use double quotes, and multi-line strings use heredocs (<<-EOT ... EOT) for things like user-data scripts and policy documents. These types are the same ones variables and outputs use, so learning them here pays off across the whole language.
References and Interpolation
The connective tissue of a configuration is references. Writing aws_vpc.main.id inside a subnet pulls that VPC's id attribute — and, crucially, tells Terraform the subnet depends on the VPC, so it builds them in the right order. Inside a string, ${...} interpolation embeds an expression: "web-${var.environment}". In modern Terraform a bare reference needs no ${} wrapper; reserve interpolation for when you are building a string around a value.
Comments, Formatting, and terraform fmt
Comments use # or // for a line and /* */ for a block. Formatting is not a matter of taste: terraform fmt rewrites files into one canonical style, which ends every alignment and indentation debate before it starts. Run it on save and enforce it in CI, and code review never spends a second on whitespace.
How Files Are Organized
Terraform loads every .tf file in a directory and merges them — the parser does not care about filenames or how you split things up. The common convention of main.tf, variables.tf, and outputs.tf is purely for humans: it makes a configuration predictable to navigate. You could put everything in one file or spread it across twenty; Terraform sees the same merged result either way.
- Treating HCL as a general-purpose language and looking for
ifandforas statements — control flow is expression-based, not imperative. - Wrapping single references in
${}everywhere in modern Terraform, cluttering expressions that read cleaner as bare references. - Assuming file boundaries matter to Terraform — splitting into many files is for readability; the parser merges them all regardless of name.
- Hand-aligning equals signs and arguing about style in review instead of running
terraform fmt. - Hardcoding an ID a sibling resource exposes as an attribute, which removes the implicit dependency and lets Terraform build things in the wrong order.
- Run
terraform fmt(and enforce it in CI) so formatting is never a review topic. - Follow the
main.tf/variables.tf/outputs.tfconvention for predictable navigation, even though Terraform ignores filenames. - Use bare references (
aws_instance.web.id) rather than wrapping single references in${}. - Reach for
forexpressions andfor_eachfor repetition instead of expecting imperative loops. - Reference other resources' attributes to express dependencies, so the graph builds itself in the right order.
Knowledge Check
In resource "aws_instance" "web", what are the two quoted parts?
- The resource type and your chosen local name for it
- The provider name and the AWS region the resource is created in
- Two arguments passed to the block
- The AMI ID and the instance type
How does Terraform treat multiple .tf files in the same directory?
- It merges them all into one configuration, ignoring filenames
- It loads only
main.tfand ignores the rest - It processes them strictly in alphabetical order as separate steps
- It requires an index file that lists which files to include
Why does HCL have no if or for statements?
- It is expression-based and declarative by design, using for expressions and conditionals as values, not control flow
- They were imperative statements removed in the Terraform 1.0 release and are scheduled to return in a future minor version
- They exist but only work inside modules
- The parser is too simple to support them
What does referencing aws_vpc.main.id from a subnet accomplish beyond reading a value?
- It creates an implicit dependency so Terraform builds the VPC before the subnet
- It copies the VPC's configuration into the subnet
- It forces both the VPC and the subnet to be destroyed and recreated together on every single apply
- Nothing — it is the same as hardcoding the ID
You got correct