The HashiCorp Certified: Terraform Associate is the one certification almost every infrastructure, platform, and DevOps role now lists, and it is unusual among cloud-era exams in two ways. First, it is genuinely useful: the objectives map almost one-to-one onto the things you do on a real Terraform team, so studying for it makes you better at the job rather than just better at the exam. Second, it is failable by competent people — not because it is hard, but because it probes the exact handful of distinctions that working engineers gloss over: when count breaks and for_each does not, what terraform init actually downloads, why terraform plan -refresh-only exists, what the difference is between terraform state rm and terraform destroy. People who have shipped Terraform for two years walk in confident and lose four or five marks on those distinctions.
This lesson is the prep kit for the 003 objective set (the version current in 2026, valid for both Terraform 1.x and OpenTofu, which the exam treats as interchangeable for the concepts it tests). It is not a re-teach of Terraform from scratch — the rest of this course does that, and the fundamentals lesson is the place to start if any concept here is unfamiliar. It is the layer on top: the objective-by-objective checklist with weightings so you know where the marks are, a bank of practice questions written in the style the real exam uses (with the reasoning for every right answer and why the tempting wrong ones are wrong), a focused tour of the topics candidates most reliably confuse, a one-page cheat sheet to revise the night before, and the exam logistics so nothing on the day surprises you. The goal is simple: pass on the first attempt, and understand why each answer is right so the knowledge survives the exam.
Learning objectives
By the end of this lesson you will be able to:
- Recite the nine objective domains of the Terraform Associate (003) and judge which carry the most weight, so your study time lands where the marks are.
- Answer multiple-choice and multiple-response questions in the exam’s own idiom, and reason about why distractors are wrong rather than just recognising the right answer.
- Cleanly separate the commonly-confused pairs the exam loves —
countvsfor_each, local vs remote state,taint/-replace, workspaces vs Terragrunt, implicit vs explicit dependencies. - Recall the core commands, meta-arguments, and functions under time pressure from a single-page cheat sheet.
- Walk into the test centre (or webcam session) knowing the format, duration, question count, cost, and rules cold.
- Map the Terraform Associate to the cloud DevOps certifications that also test Terraform, so you can plan a credential path.
Prerequisites
This is the capstone of the KloudVin Terraform Zero-to-Hero course, so the assumption is that you have worked through (or already know) the material in the earlier lessons: HCL and the core workflow, modules, state at scale, and troubleshooting. If you have run init/plan/apply/destroy for real, written at least one module with typed variables and outputs, and configured a remote backend once, you are ready. If any of those are shaky, study that lesson first — the exam rewards hands-on muscle memory more than memorisation, and a fortnight of building beats a month of flashcards. HashiCorp recommends roughly six months of hands-on Terraform before sitting the exam, but motivated engineers with a real project pass comfortably sooner.
How the exam is built: the objective domains and their weight
The 003 blueprint is published by HashiCorp as nine objective domains. The exam does not publish a per-domain percentage the way AWS does, but the relative emphasis is well established from the objective wording and the experience of thousands of candidates. The table below gives each domain, what it actually covers, and a realistic weight band so you can prioritise. Treat the bands as “where the marks cluster”, not gospel percentages.
| # | Objective domain | What it covers | Weight band |
|---|---|---|---|
| 1 | Understand IaC concepts | Declarative vs imperative; what IaC buys you (versioning, repeatability, self-service); idempotency | Low |
| 2 | Understand the purpose of Terraform (vs other IaC) | Multi-cloud/provider-agnostic; provisioning vs configuration management; the provider ecosystem; benefits of state | Low |
| 3 | Understand Terraform basics | Providers + version constraints; terraform init and what it downloads; plugin/provider installation; provider authentication |
High |
| 4 | Use Terraform outside core workflow | import, state subcommands (mv/rm/list/show), taint/-replace, terraform fmt, validate, workspaces (CLI), -refresh-only |
High |
| 5 | Interact with Terraform modules | Module sources (registry, Git, local), versions, inputs/outputs, calling modules, the public registry | Medium |
| 6 | Use the core Terraform workflow | write → plan → apply; init, plan, apply, destroy; -target, -var/-var-file; auto-approve; the dependency graph |
High |
| 7 | Implement and maintain state | Local vs remote backends; state locking; sensitive data in state; backend block; state migration; terraform_remote_state |
High |
| 8 | Read, generate, and modify configuration | Variables (types, validation, sensitive), outputs, locals, data sources, built-in functions, count/for_each/dynamic, resource addressing, dependencies |
High |
| 9 | Understand HCP Terraform / Terraform Cloud capabilities | Remote runs, remote state, private registry, Sentinel/policy, workspaces in HCP, VCS-driven runs | Medium |
The pattern to read off this table: the marks live in the mechanics of doing the work — domains 3, 4, 6, 7, and 8. The conceptual domains (1 and 2) are quick marks you should bank with minimal study. HCP/Terraform Cloud (domain 9) is worth a focused hour even if you only ever use open-source Terraform, because it always appears and is easy to neglect. If your time is short, over-invest in state (7), configuration constructs (8), and the outside-core-workflow commands (4) — that is where prepared candidates separate from unprepared ones.
A useful way to structure revision is by failure mode. The conceptual domains fail from carelessness (reading too fast); the mechanical domains fail from genuine gaps. Spend your last week on the mechanics.
The question formats you will see
Knowing the shapes in advance removes a surprising amount of exam-day friction:
| Format | How it appears | Tactic |
|---|---|---|
| Multiple choice | One correct answer of four | Eliminate two obviously wrong, then decide between the two plausible — that is where the real test is |
| Multiple response | “Select TWO” / “Select THREE” — partial credit is not given | The stem tells you how many; if you can only justify one, you are missing something |
| True / False | A single statement to judge | Watch absolute words — “always”, “never”, “must” are often the falsifier |
| Fill in the blank | Type the exact command/keyword | Spelling and exact subcommand matter (terraform state mv, not move) |
| Text-match / scenario | A short scenario, pick the command or behaviour | Map the scenario to the minimal correct action |
There are no hands-on labs and no code you must write from a blank screen — it is entirely recognition and recall, which is why a cheat sheet and targeted practice questions are the highest-leverage prep.
Practice question bank (with explained answers and why distractors fail)
These are written in the exam’s idiom and ordered roughly by domain. Read each, commit to an answer before reading the explanation, and pay as much attention to why the wrong answers are wrong as to the right one — that habit is what the exam rewards. Answers and full reasoning follow each block.
Q1 — Core basics (domain 3)
Which command downloads provider plugins and initialises the backend, and must be run before plan or apply in a fresh working directory?
- A.
terraform get - B.
terraform init - C.
terraform refresh - D.
terraform providers
Answer: B. terraform init is the first command in any working directory: it downloads the providers declared in required_providers, installs modules, and initialises the configured backend. A (terraform get) only downloads/updates modules — it does not install providers or initialise the backend, so it is insufficient. C (terraform refresh) is deprecated in favour of plan -refresh-only/apply -refresh-only and reconciles state with reality; it neither installs plugins nor initialises a backend. D (terraform providers) prints the providers a configuration requires — it is a reporting command, not an installer. The trap here is that all four are real commands, so recognition alone is not enough; you have to know what each does.
Q2 — count vs for_each (domain 8, the classic)
You manage five S3 buckets using count over a list. You delete the second element from the list and run apply. What happens?
- A. Only the second bucket is destroyed; the rest are untouched.
- B. The second bucket is destroyed and recreated with the same name.
- C. Terraform destroys and recreates buckets 2–5 because the list indices shift.
- D. Nothing changes because
countis order-independent.
Answer: C. With count, each instance is addressed by its list index (aws_s3_bucket.this[0], [1], …). Removing the second element shifts every later element down by one index, so Terraform sees [1] now pointing at what used to be [2], [2] at the old [3], and so on — it plans to destroy and recreate everything from the removed position onward, and destroys the now-surplus last index. A is what you wanted and what for_each would have given you, but count cannot do it. B understates the blast radius — it is not one bucket, it is the tail of the list. D is wrong because count is emphatically index-dependent, which is the whole point of the question. The lesson the exam is teaching: use for_each (keyed by a stable string) when elements can be added or removed from the middle of a collection.
Q3 — for_each requirements (domain 8)
Which of the following can you pass to for_each? (Select TWO.)
- A. A
list(string) - B. A
map(string) - C. A
set(string) - D. A
number
Answer: B and C. for_each accepts a map or a set of strings — each element must have a unique, known string key that becomes the instance address (resource.name["key"]). A is the trap: a list is not directly accepted; you must convert it with toset(var.list) first (and only if the values are unique). D is simply the wrong type — a number is not a collection. Knowing that for_each needs a map/set, and that the idiomatic way to feed it a list is toset(...), is one of the highest-frequency facts on the exam.
Q4 — state, sensitive data (domain 7)
A teammate says “we don’t need to secure the state backend because we never put passwords in Terraform.” Why are they wrong?
- A. Terraform encrypts state, so the backend is irrelevant.
- B. State can contain sensitive values (e.g. generated passwords, connection strings, resource attributes) in plaintext, regardless of intent.
- C. State only contains resource IDs, which are not sensitive.
- D. The
sensitive = trueflag removes secrets from state.
Answer: B. Terraform writes resource attributes into state, and many of those are sensitive even when you never typed a secret yourself — an RDS instance’s initial password, a generated random_password, a connection string, an access key returned by a provider. All of it lands in state in plaintext. That is precisely why the backend must be encrypted and access-controlled. A is false: open-source Terraform does not encrypt local state at rest by default (remote backends like S3/Azure Blob/GCS can be configured to encrypt, but that is the backend’s feature, not Terraform’s). C is false — state holds far more than IDs. D is the subtle trap: sensitive = true only redacts a value from CLI output; it does not remove or encrypt the value in the state file. This four-way distinction is a favourite.
Q5 — taint vs replace (domain 4)
You want to force a single resource to be destroyed and recreated on the next apply, using the current, recommended approach. What do you do?
- A.
terraform taint aws_instance.web - B.
terraform apply -replace="aws_instance.web" - C.
terraform destroy -target="aws_instance.web"thenapply - D. Delete the resource from state with
terraform state rm, thenapply
Answer: B. The current, recommended way to force replacement is the -replace flag on plan/apply, because it shows you the replacement in the plan before you commit to it. A (terraform taint) does the same thing but is deprecated in favour of -replace; the exam tests that you know -replace is the modern equivalent (it may still ask what taint did). C destroys the resource and recreates it on the next apply, but it does so in two steps with a window where the resource does not exist, and -target is a blunt instrument meant for recovery, not routine replacement — not the clean single-operation answer. D is actively dangerous: state rm only makes Terraform forget the resource (it keeps existing in the cloud, now unmanaged), so the next apply creates a duplicate rather than replacing the original. Knowing that taint ≡ -replace (one deprecated, one current) and that state rm ≠ replacement is the exam’s intent.
Q6 — local vs remote state (domain 7)
Three engineers share a Terraform configuration. Which single statement is the strongest reason to use a remote backend rather than local state?
- A. Remote state runs
applyfaster. - B. Remote state provides a shared source of truth and state locking, preventing concurrent writes from corrupting state.
- C. Remote state is required to use modules.
- D. Local state cannot store outputs.
Answer: B. The defining reasons for a remote backend on a team are a shared state everyone reads/writes and locking so two applys cannot run at once and corrupt the file. A is false — remote state does not make apply faster (it can add a little latency). C is false: modules work fine with local state. D is false: local state stores outputs perfectly well. The exam wants you to connect “team” → “remote + locking”, and to not be tempted by plausible-sounding but incorrect benefits.
Q7 — workspaces (domain 4)
Which statement about CLI workspaces (terraform workspace) is correct?
- A. Each workspace uses a completely separate backend configuration.
- B. Workspaces let one configuration maintain multiple distinct state files (e.g.
default,dev,prod) within the same backend, selectable at runtime. - C. Workspaces are the recommended way to isolate production from development at scale.
- D. The active workspace name is unavailable inside the configuration.
Answer: B. A workspace gives you a separate state instance for the same configuration and same backend — you switch with terraform workspace select and Terraform stores each workspace’s state under a distinct key. A is false: workspaces share one backend; they do not each get their own backend block. C is the trap the exam plants deliberately — HashiCorp’s own guidance is that workspaces are not ideal for strong prod/dev isolation (they share backend, credentials, and blast radius), and teams typically use separate directories/backends or a tool like Terragrunt for real environment isolation. D is false: terraform.workspace exposes the current workspace name inside the configuration. The high-value fact: workspaces ≠ environment isolation at scale.
Q8 — modules and versioning (domain 5)
When sourcing a module from the public Terraform Registry, which argument lets you pin a specific, reproducible version?
- A.
version(e.g.version = "5.1.2") - B.
refin the source URL - C.
tag - D.
revision
Answer: A. Registry modules are pinned with the version argument (and it supports constraints like ~> 5.1). B (ref) is how you pin a Git-sourced module (source = "git::https://…//module?ref=v5.1.2"), not a registry module — a classic “right keyword, wrong source type” distractor. C and D are not Terraform module-pinning arguments at all. The distinction the exam draws: version for the registry, ?ref= for Git.
Q9 — the dependency graph (domain 6/8)
A security group must exist before the instance that references it. You wrote vpc_security_group_ids = [aws_security_group.web.id] on the instance. What ensures correct ordering?
- A. You must add
depends_on = [aws_security_group.web]. - B. The implicit dependency created by referencing
aws_security_group.web.idorders them automatically. - C. Resources are created in file order, so put the SG first.
- D. Nothing — Terraform creates everything in parallel regardless.
Answer: B. Referencing one resource’s attribute in another (aws_security_group.web.id) creates an implicit dependency; Terraform builds a dependency graph from those references and orders creation correctly without any extra directive. A is unnecessary here — depends_on is for dependencies Terraform cannot infer (e.g. a hidden side-effect with no attribute reference); adding it when an implicit dependency already exists is redundant and a mild anti-pattern. C is false: Terraform ignores file/line order and works from the graph. D is false: Terraform parallelises independent resources but strictly orders dependent ones. The exam’s point: prefer implicit dependencies; reserve depends_on for what the graph can’t see.
Q10 — variables and precedence (domain 8)
Which variable source has the highest precedence (wins when the same variable is set in several places)?
- A.
terraform.tfvars - B. Environment variables (
TF_VAR_name) - C. A
-varflag on the command line - D.
defaultin thevariableblock
Answer: C. The -var (and -var-file) command-line flags have the highest precedence and override everything else. The order, lowest to highest, is: environment variables → terraform.tfvars → terraform.tfvars.json → *.auto.tfvars (alphabetical) → -var/-var-file (last on the command line wins). D (default) is the lowest — it is only used when nothing else sets the variable. A and B sit in the middle. Memorising “command line wins, default loses” handles most precedence questions.
Q11 — HCP Terraform / Terraform Cloud (domain 9)
Which capability is provided by HCP Terraform (Terraform Cloud) but not by open-source Terraform on its own?
- A. The
planandapplycommands - B. Remote state storage
- C. Remote runs with policy enforcement (Sentinel/OPA) and a private module registry
- D. The
for_eachmeta-argument
Answer: C. HCP Terraform adds remote execution of runs, policy-as-code enforcement (Sentinel, or OPA), a private module registry, run history, and team/governance features on top of Terraform. A and D are core CLI features available to everyone. B is the trap: open-source Terraform can store remote state in many backends (S3, Azure Blob, GCS, Consul, …) — remote state is not exclusive to HCP, even though HCP also offers it. The exam wants you to separate “things only the platform adds” (governance, remote runs, private registry) from “things plain Terraform already does”.
Q12 — outside core workflow (domain 4)
You created an S3 bucket manually in the console and now want Terraform to manage it without destroying and recreating it. What is the correct approach?
- A.
terraform apply— Terraform will adopt it automatically. - B. Write the matching resource block, then
terraform import aws_s3_bucket.this <bucket-name>(or use animportblock). - C.
terraform refresh - D.
terraform state mv
Answer: B. To bring an existing, unmanaged resource under Terraform you import it: write a resource block that matches it, then run terraform import <address> <id> (or, in Terraform 1.5+, declare an import { to = … id = … } block and apply). A is false: a bare apply does not adopt pre-existing resources — it would try to create a new bucket and conflict. C (refresh) only updates state for resources Terraform already manages; it cannot adopt a new one. D (state mv) renames/moves an address that is already in state — it has nothing to do with importing something not yet tracked. The distinction between import (bring in something new) and state mv (move something already tracked) is frequently tested.
Q13 — functions (domain 8)
Which expression safely returns the value of var.region if set, otherwise the string "ap-south-1"?
- A.
try(var.region, "ap-south-1") - B.
coalesce(var.region, "ap-south-1") - C.
lookup(var.region, "ap-south-1") - D.
default(var.region, "ap-south-1")
Answer: B. coalesce() returns the first non-null/non-empty argument, so it returns var.region when set and the fallback otherwise — the idiomatic “value or default”. A (try) returns the first argument that evaluates without error; it is for swallowing errors (e.g. indexing something that might not exist), not for null/empty fallback, so it is the wrong tool here even though it sometimes appears to work. C (lookup) is for maps — it returns a key’s value or a default from a map, not a scalar fallback. D is not a real Terraform function. The exam tests that you reach for coalesce for “value-or-default” and try for “expression-might-error”.
Q14 — plan behaviour (domain 6)
What does terraform plan -refresh-only do?
- A. Shows what would change to make infrastructure match the configuration.
- B. Shows only the differences between prior state and real infrastructure (i.e. drift), proposing to update state — not the configuration.
- C. Deletes resources not in the configuration.
- D. Refreshes provider plugins.
Answer: B. -refresh-only compares state against real infrastructure and proposes to update the state file to match reality — it is the modern, reviewable replacement for the deprecated terraform refresh, and it is how you detect drift without changing your config. A describes a normal plan. C describes (loosely) what removing config + apply would do — -refresh-only never destroys anything. D is terraform init -upgrade, unrelated. Knowing that -refresh-only reconciles state ↔ reality (not config → reality) is the point.
Q15 — provider version constraints (domain 3)
In required_providers, what does version = "~> 4.16" permit?
- A. Exactly
4.16only. - B. Any version
>= 4.16and< 5.0(the rightmost component may increase). - C. Any version
>= 4.16and< 4.17. - D. Any
4.xor5.x.
Answer: B. The pessimistic constraint ~> 4.16 allows the rightmost specified component to increase while the ones to its left stay fixed — so ~> 4.16 means >= 4.16, < 5.0 (4.16, 4.17, …, 4.99). Contrast ~> 4.16.0, which means >= 4.16.0, < 4.17.0. A would be version = "4.16" with =. C is what ~> 4.16.0 (three components) would mean — a common mix-up. D has no upper bound on the major version, which ~> never allows. The exam reliably tests the ~> operator and the two-component vs three-component difference.
That is a representative cross-section. If you can answer these and explain the distractors, you are tracking well. Sit at least one full-length timed practice exam (the community ones and HashiCorp’s sample questions are fine) so that pacing — roughly a minute a question — becomes automatic.
Commonly-confused topics (the marks most candidates drop)
The exam is built around a small set of distinctions that look similar and behave differently. Internalise these and you reclaim the marks that prepared-but-not-careful candidates lose.
count vs for_each
count |
for_each |
|
|---|---|---|
| Takes | A whole number | A map or set of strings |
| Instance address | By index: res[0], res[1] |
By key: res["dev"], res["prod"] |
| Add/remove in the middle | Shifts indices → destroys/recreates the tail | Only the affected key changes — others untouched |
| Use when | A fixed count of near-identical things; or a simple on/off toggle (count = var.enabled ? 1 : 0) |
Items keyed by a stable identifier that may grow/shrink |
| Feeding a list | n/a | Convert with toset(var.list) |
The single most important takeaway: if elements can be added or removed from anywhere but the end, use for_each. Using count over a mutable list is the source of countless real-world “why did Terraform destroy half my infrastructure” incidents — and the exam knows it.
Local state vs remote state
| Local state | Remote state | |
|---|---|---|
| Where | terraform.tfstate on disk |
S3 / Azure Blob / GCS / Consul / HCP, etc. |
| Locking | None (single-user only) | Yes (prevents concurrent writes) |
| Sharing | Not shareable safely | Shared source of truth for the team |
| Encryption at rest | Not by default | Depends on backend (S3 SSE, Azure encryption, etc.) |
| Use when | Solo experiment, learning | Any team or production work |
Two exam-grade facts: open-source Terraform does not encrypt local state at rest by default, and remote state’s headline benefits are sharing and locking — not speed.
taint / -replace (and what they are not)
terraform taint <addr> marks a resource for destruction-and-recreation on the next apply. It is deprecated; the modern equivalent is terraform apply -replace="<addr>", which is better because the replacement appears in the plan before you approve it. Crucially, replacement is not the same as:
terraform state rm <addr>— Terraform forgets the resource; it stays in the cloud, unmanaged. Next apply creates a duplicate.terraform destroy -target=<addr>— destroys now, recreates next apply, with a gap where it doesn’t exist.terraform import— the opposite direction: brings an existing unmanaged resource into state.
Expect at least one question that hinges on state rm ≠ replacement.
Workspaces vs Terragrunt (environment isolation)
This pairing trips up experienced engineers because both relate to “managing multiple environments”, but they operate at different levels.
| CLI workspaces | Terragrunt | |
|---|---|---|
| What it is | A built-in Terraform feature: multiple state files for one config in one backend | A third-party wrapper around Terraform |
| Isolation | Weak — shares backend, credentials, blast radius | Strong — separate backends/state per environment, DRY config, dependencies |
| Backend config | One backend, switched by workspace | Generated per-unit; different state keys/accounts |
| Inter-environment deps | Not handled | dependency/dependencies blocks pass outputs |
| HashiCorp’s stance | Fine for light variation; not recommended as the primary prod/dev isolation mechanism at scale | (Not a HashiCorp product; see the Terragrunt lesson) |
For the exam, the load-bearing fact is about workspaces: they give you multiple state files for one configuration, and HashiCorp explicitly cautions that they are not the right tool for strong production isolation. Terragrunt itself is out of scope for the Associate exam (it is a third-party tool) — but understanding the contrast cements why workspaces fall short, which is exactly what a workspace question is testing.
Implicit vs explicit dependencies
- Implicit — created automatically when one resource references another’s attribute (
subnet_id = aws_subnet.a.id). This is the normal, preferred mechanism; Terraform builds its graph from these references. - Explicit —
depends_on = [aws_iam_role_policy.x], used only when there is a real ordering requirement Terraform cannot infer (a hidden side effect with no attribute reference). Overusingdepends_onwhen an implicit dependency already exists is an anti-pattern and can reduce parallelism.
The exam wants you to choose the minimal correct mechanism: reach for depends_on only when there is no attribute to reference.
A few more high-frequency confusions
terraform fmtvsterraform validate—fmtrewrites files to canonical style (formatting only);validatechecks the configuration is internally consistent and syntactically valid (it does not access remote state or providers’ APIs).terraform planvsterraform plan -refresh-only— the former plans config→reality changes; the latter only reconciles state↔reality (drift).- Provider vs provisioner — a provider is the plugin that talks to an API (AWS, Azure, …); a provisioner (
local-exec/remote-exec) runs scripts and is a last resort HashiCorp explicitly discourages. variablevslocalvsoutput— inputs you can set from outside; computed values reused within the config; values exposed to callers/CLI.
The diagram lays the Terraform Associate at the foundation of a credential ladder, then shows how the cloud DevOps professional exams (AWS DOP-C02, Azure AZ-400, Google Cloud DevOps) build on it — the same Terraform knowledge feeds every rung above.
One-page cheat sheet
Revise this the night before. If you can reproduce it from memory, you are ready.
Core workflow commands
| Command | Does |
|---|---|
terraform init |
Download providers + modules, init backend (run first; -upgrade to bump versions) |
terraform validate |
Check config is valid & consistent (no API calls) |
terraform fmt |
Rewrite files to canonical formatting |
terraform plan |
Show what apply would do (-out=plan.tfplan to save) |
terraform apply |
Make reality match config (-auto-approve to skip prompt; apply a saved plan: apply plan.tfplan) |
terraform destroy |
Destroy everything in state (-target to scope) |
terraform output |
Print output values (-json, -raw) |
terraform show |
Human-readable state or a saved plan |
Outside-core-workflow commands
| Command | Does |
|---|---|
terraform import <addr> <id> |
Bring an existing resource into state |
terraform state list |
List addresses in state |
terraform state show <addr> |
Show one resource’s state |
terraform state mv <a> <b> |
Rename/move an address (refactor) |
terraform state rm <addr> |
Forget a resource (stays in cloud, unmanaged) |
terraform apply -replace="<addr>" |
Force destroy+recreate (modern; replaces deprecated taint) |
terraform plan -refresh-only |
Detect drift (state ↔ reality), update state only |
terraform workspace [new|select|list] |
Manage multiple state files for one config |
terraform graph |
Emit the dependency graph (DOT) |
terraform providers |
Report required providers |
Meta-arguments (usable in resource/module blocks)
| Meta-argument | Purpose |
|---|---|
count |
N instances, addressed by index |
for_each |
One instance per map/set element, addressed by key |
depends_on |
Explicit ordering Terraform can’t infer |
provider |
Select a non-default (aliased) provider |
lifecycle |
create_before_destroy, prevent_destroy, ignore_changes, replace_triggered_by |
Functions worth memorising
| Function | Use |
|---|---|
coalesce(a, b, …) |
First non-null/empty (value-or-default) |
try(a, b, …) |
First that evaluates without error |
lookup(map, key, default) |
Map value or default |
merge(m1, m2, …) |
Combine maps |
concat(l1, l2, …) |
Combine lists |
toset(list) |
List → set (to feed for_each) |
length(x) |
Count of list/map/string |
join / split |
Strings ↔ lists |
file() / templatefile() |
Read/render a file |
jsonencode / jsondecode |
To/from JSON |
cidrsubnet / cidrhost |
Subnet/host maths |
Variable precedence (low → high)
env (TF_VAR_*) → terraform.tfvars → *.tfvars.json → *.auto.tfvars (alphabetical) → -var / -var-file (command line; last wins). default in the block is the fallback when nothing sets it.
Version constraint operators
= 1.2.3 exact · >=, <=, >, < bounds · != 1.2.3 exclude · ~> 1.2 means >= 1.2, < 2.0 · ~> 1.2.3 means >= 1.2.3, < 1.3.0.
State facts
Local state = no locking, single user, not encrypted at rest by default. Remote state = sharing + locking; encryption depends on the backend. State holds sensitive values in plaintext regardless of intent; sensitive = true only redacts output, not the state file.
Hands-on lab: a self-test sandbox using only the local provider
You cannot practise the exam in a lab (it is recognition/recall), but you can drill the highest-yield commands with zero cloud cost using the built-in local, random, and null providers — no credentials, no spend. This 15-minute drill exercises init, plan, apply, count/for_each behaviour, state subcommands, -replace, and -refresh-only exactly as the exam describes them.
Step 1 — scaffold. Create a working directory and a single file:
# main.tf
terraform {
required_version = ">= 1.6.0"
required_providers {
local = { source = "hashicorp/local", version = "~> 2.5" }
random = { source = "hashicorp/random", version = "~> 3.6" }
}
}
variable "names" {
type = set(string)
default = ["alpha", "beta", "gamma"]
}
# for_each keyed by a stable string — the exam-correct pattern
resource "local_file" "note" {
for_each = var.names
filename = "${path.module}/out/${each.key}.txt"
content = "hello ${each.key}\n"
}
resource "random_pet" "name" {
length = 2
}
output "pet" {
value = random_pet.name.id
}
Step 2 — the core loop. Run, observing each command’s output:
terraform init # downloads local + random providers; note the .terraform.lock.hcl created
terraform validate # "Success! The configuration is valid."
terraform fmt # no change if already formatted
terraform plan # 4 to add (3 files + 1 pet)
terraform apply -auto-approve
Expected output: three files appear under out/, and terraform output pet prints a two-word pet name. Confirm: terraform state list shows local_file.note["alpha"], ["beta"], ["gamma"], and random_pet.name.
Step 3 — prove the for_each lesson. Remove "beta" from the names default and run terraform plan. Observe that only local_file.note["beta"] is planned for destruction — the others are untouched, because for_each keys are stable. (Mentally contrast: with count, removing the middle element would churn the tail. This is Q2 made real.) Apply it.
Step 4 — drill the outside-core commands.
# Force one resource to be recreated the modern way:
terraform apply -replace="random_pet.name" -auto-approve # pet name changes; files unaffected
# Detect drift: delete a managed file by hand, then refresh-only:
rm out/alpha.txt
terraform plan -refresh-only # shows state reconciling to reality (file now absent)
terraform plan # shows it would RECREATE alpha.txt to match config
# State surgery without touching real resources:
terraform state show 'local_file.note["gamma"]'
Step 5 — Cleanup.
terraform destroy -auto-approve # removes all managed files + pet
Then delete the directory. Cost note: zero. The local/random/null providers run entirely on your machine — no cloud account, no API calls, no spend. This is the ideal way to keep command muscle memory sharp while revising.
Common mistakes & troubleshooting
| Symptom (in study or on the day) | Cause | Fix |
|---|---|---|
| Recognise the right answer but pick a distractor under time pressure | Reading the stem too fast; missing “NOT”/“EXCEPT”/“Select TWO” | Read the stem twice; underline qualifiers; confirm how many answers are wanted |
Confusing count and for_each behaviour |
Treating them as interchangeable | Memorise: count=index (churns tail), for_each=key (stable) |
Thinking sensitive = true secures state |
Conflating output redaction with at-rest protection | It only hides CLI output; state still holds plaintext — secure the backend |
Picking terraform refresh answers |
Studying old material | refresh is deprecated → plan/apply -refresh-only |
Choosing taint as the “correct” current method |
Pre-1.x habits | taint is deprecated → apply -replace= |
| Believing remote state is required for modules | Over-association | Modules work with local state; remote state is about sharing+locking |
Assuming state rm deletes the cloud resource |
Misreading the command | state rm only forgets it; destroy/console deletes it |
Mixing up ~> 1.2 and ~> 1.2.3 |
Skimming the constraint operator | Two components → < 2.0; three components → < 1.3.0 |
| Running out of time | No pacing practice | One mark/minute; flag and skip hard ones, return at the end |
Best practices for passing (and for the job)
- Build, then revise. A real project (even the portfolio module from the previous lesson) teaches the mechanics far faster than flashcards. The exam rewards people who have felt a
countre-index destroy something. - Drill the deprecated→current pairs.
refresh→-refresh-onlyandtaint→-replaceare guaranteed marks if you know them and easy traps if you don’t. - Read the whole stem, then all options. The first plausible answer is often a distractor; the correct one is sometimes “more correct”.
- Time-box practice exams. Pacing at ~1 minute/question removes day-of anxiety. Flag-and-return is your friend.
- Don’t neglect HCP/Terraform Cloud. Even pure open-source users get questions on it; one focused hour banks easy marks.
- Know the cheat sheet cold. The single page above covers the large majority of recall questions.
Security notes
A few security facts are themselves exam-testable and job-critical, so they earn their own section:
- State contains secrets in plaintext. Generated passwords, connection strings, and key material land in state regardless of whether you typed them. Always use an encrypted, access-controlled remote backend, restrict who can read it, and never commit
terraform.tfstateto Git. sensitive = trueis output hygiene, not encryption. It redacts values fromplan/apply/outputso they don’t leak into logs and CI output — but the value is still in state. Treat the two concerns separately.- Prefer keyless/short-lived credentials. In CI, use OIDC-based auth to the cloud rather than long-lived static keys (covered in the CI/CD lesson). The exam touches credential handling under “provider authentication”.
- Provisioners are a last resort.
local-exec/remote-execrun arbitrary commands and bypass Terraform’s model; HashiCorp explicitly discourages them. The exam expects you to know they are a fallback, not a first choice. - Pin provider and module versions. Unpinned versions let a surprise upgrade change behaviour; the lock file (
.terraform.lock.hcl) should be committed.
Interview & exam questions
A blend of likely exam items and the interview questions that probe the same knowledge — with concise answers.
- What does
terraform initdo, and why must it run first? Downloads providers (perrequired_providers) and modules, and initialises the backend. Without it,plan/applyhave no plugins and no backend, so they cannot run. countvsfor_each— when doescountbite you? When you add/remove an element anywhere but the end of a list: indices shift and Terraform destroys/recreates the tail. Usefor_each(keyed by a stable string) for collections that change.- Why secure the state backend even if you never store passwords? State holds sensitive resource attributes (generated passwords, connection strings) in plaintext regardless of intent. Encrypt and access-control the backend.
- Difference between
terraform taintandterraform apply -replace? Same effect (force destroy+recreate), buttaintis deprecated;-replaceis current and shows the replacement in the plan before you approve. - What’s the modern way to detect drift?
terraform plan -refresh-only— it reconciles state with real infrastructure and proposes to update state, replacing the deprecatedterraform refresh. - Implicit vs explicit dependencies? Implicit dependencies come from attribute references and are preferred;
depends_onis for ordering Terraform cannot infer. Overusingdepends_onis an anti-pattern. - Are CLI workspaces a good prod/dev isolation mechanism? Not at scale — they share a backend, credentials, and blast radius. HashiCorp recommends separate directories/backends (or a tool like Terragrunt) for strong isolation.
- What does
sensitive = trueactually protect? It redacts a value from CLI output so it doesn’t leak into logs — it does not encrypt or remove the value in the state file. - How do you bring an existing, manually-created resource under Terraform? Write a matching resource block, then
terraform import <addr> <id>(or a 1.5+importblock + apply). A bareapplywould try to create a duplicate. - What does
~> 4.16allow, and how does~> 4.16.0differ?~> 4.16=>= 4.16, < 5.0;~> 4.16.0=>= 4.16.0, < 4.17.0. The rightmost specified component is the one allowed to increase. - What does HCP Terraform add over open-source? Remote runs, policy-as-code (Sentinel/OPA), a private module registry, run history, and governance — note remote state alone is not exclusive to HCP.
- Variable precedence, lowest to highest?
default< environment (TF_VAR_*) <terraform.tfvars/.json<*.auto.tfvars<-var/-var-file(command line wins; last one on the line wins).
Quick check
- You remove the middle element from a list used by
count. What does Terraform plan to do? - Which command is the current replacement for
terraform taint? - Does
sensitive = trueencrypt the value in the state file? - What does
~> 1.4permit as a version range? - Which has higher precedence: a
-varflag orterraform.tfvars?
Answers
- Destroy and recreate every element from the removed index onward (and destroy the now-surplus last index), because
countaddresses by index and the indices shift.for_eachwould have changed only the affected element. terraform apply -replace="<address>"(plan -replaceto preview).- No — it only redacts the value from CLI output (
plan/apply/output); the value remains in plaintext in state. Secure the backend. >= 1.4, < 2.0— the pessimistic operator lets the rightmost specified component (here the minor) increase up to the next major.- The
-varcommand-line flag — command-line variables have the highest precedence and overrideterraform.tfvars.
Exercise
Block out a one-week sprint to the exam and execute it:
- Self-assess (day 1). Sit a full-length timed practice exam cold. Record your score per objective domain.
- Target the gaps (days 2–4). For every domain under ~80%, do the matching course lesson and run the relevant part of the lab above. Pay special attention to domains 4, 7, and 8.
- Drill the confusions (day 5). Re-derive the “commonly-confused” tables from memory:
count/for_each, local/remote state,taint/-replace, workspaces/Terragrunt, implicit/explicit deps. If you can reproduce them, you own them. - Memorise the cheat sheet (day 6). Reproduce the one-pager from a blank page until it is automatic.
- Dress rehearsal (day 7). Sit a second timed practice exam at the same time of day you’ve booked the real one. Aim for ≥85% with time to spare. Book the exam.
Deliverable: a filled-in score-by-domain table from days 1 and 7, plus your hand-written cheat sheet — proof you can recall it under pressure.
Certification mapping
The Terraform Associate is the credential this whole course leads to, and it also underpins a wider DevOps credential path. Terraform shows up directly or indirectly in several cloud DevOps exams:
| Certification | Where Terraform fits |
|---|---|
| HashiCorp Certified: Terraform Associate (003) | The target of this lesson — the canonical Terraform credential |
| AWS Certified DevOps Engineer – Professional (DOP-C02) | IaC is a core domain; Terraform (alongside CloudFormation/CDK) is expected fluency |
| Microsoft Certified: DevOps Engineer Expert (AZ-400) | Tests IaC in pipelines; Terraform is a first-class option on Azure DevOps/GitHub |
| Google Cloud Professional Cloud DevOps Engineer | IaC and automation feature throughout; Terraform is Google’s recommended multi-cloud IaC |
| HashiCorp Certified: Terraform Authoring & Operations Professional | The next-level HashiCorp exam — deeper authoring, modules, and operations beyond the Associate |
For a consolidated, exam-by-exam study plan across the cloud DevOps and Kubernetes certifications (with the same objective-checklist treatment), see the DevOps certification prep kit.
Glossary
- Objective domain — one of the nine published areas the exam blueprint is organised around.
- Multiple response — a question requiring more than one selected answer (“Select TWO”); no partial credit.
- Distractor — a deliberately plausible but incorrect option; reasoning about why it’s wrong is the skill the exam tests.
count— meta-argument creating N instances addressed by integer index.for_each— meta-argument creating one instance per element of a map/set, addressed by string key.- State — Terraform’s record mapping declared resources to real-world objects and their attributes.
- Remote backend — shared, lockable storage for state (S3, Azure Blob, GCS, HCP, …).
- Drift — divergence between Terraform’s state and the real infrastructure; surfaced by
plan -refresh-only. -replace— the current flag to force a resource’s destruction and recreation (replaces deprecatedtaint).import— bringing an existing, unmanaged resource into Terraform state.- CLI workspace — multiple named state files for one configuration within one backend.
- HCP Terraform (Terraform Cloud) — HashiCorp’s hosted platform adding remote runs, policy enforcement, and a private registry.
- Pessimistic constraint (
~>) — a version operator allowing the rightmost specified component to increase. - OpenTofu — the open-source fork of Terraform; conceptually interchangeable for everything the Associate exam tests.
Next steps
This is the final lesson of the KloudVin Terraform Zero-to-Hero course — congratulations on reaching it. From here:
- Book the exam while the material is fresh, and run the one-week sprint in the Exercise above.
- If you want a portfolio to point at in interviews, build the projects in Real-World Terraform Portfolio Projects.
- To broaden into the wider toolchain, the DevOps certification prep kit covers the AWS/Azure/GCP DevOps and Kubernetes (CKA/CKAD/CKS) exams with the same objective-checklist approach.
- Going deeper on Terraform itself, the architecting ladder shows how the concepts you’ve just been tested on scale from a single module to an enterprise IaC platform.