Terraform interview questions — live practice quiz
Reviewed by Mark Dickie · Last updated
Terraform is an open-source infrastructure-as-code tool from HashiCorp that lets you define, provision, and manage cloud and on-prem resources using declarative HCL configuration files. For an interview you should be able to explain the core workflow (init → plan → apply → destroy), how state tracking works and why remote state with locking matters, the difference between resources and data sources, and how modules help you structure reusable configuration. Expect questions on dependency resolution, variable and output passing, the provider plugin model, and common pitfalls like state drift and resource tainting.
| Concept | What interviewers test |
|---|---|
| State file | Where it lives, why it is sensitive, how terraform refresh and plan interact with it |
| Remote backends | S3 + DynamoDB locking, Terraform Cloud, Azure Storage, GCS — and why locking prevents corruption |
| Resources vs. data sources | resource creates something; data reads something that already exists |
| Modules | Input variables, outputs, version pinning, and when to split a config into modules |
| Workspaces | What they do, their limits, and why teams often prefer separate state files over workspaces |
| Lifecycle meta-arguments | create_before_destroy, prevent_destroy, ignore_changes, and count/for_each |
| Import | Bringing an existing cloud resource under Terraform management without recreating it |
How does Terraform state work and why does it matter?
State is the JSON file Terraform uses to map your HCL resource blocks to real infrastructure objects. It stores every resource's current attributes so that terraform plan can compute the diff between your configuration and reality. Because state contains sensitive values (passwords, keys, IP addresses) and is the single source of truth, storing it in a shared, locked backend is a core operational practice.
- Local state lives in
terraform.tfstateon disk — fine for learning, bad for teams. - Remote backends (S3, GCS, Azure Blob, Terraform Cloud) store state centrally and enable collaboration.
- State locking prevents two concurrent
applyruns from corrupting the file. Most remote backends support it natively; a local file backend does not. terraform importpulls an existing resource into state, but does not generate the HCL for it — you write the config block yourself.terraform statesubcommands (list,show,mv,rm) let you inspect and edit state directly when refactoring modules or fixing drift.
What is the difference between count, for_each, and depends_on?
count creates a numbered set of resource instances from an integer; for_each creates keyed instances from a map or set of strings, which is safer because adding or removing items does not renumber the rest. depends_on is a meta-argument that forces an explicit dependency when Terraform cannot infer one from input references — useful for resources that depend on side effects of a provisioner or an external API call.
How do you handle sensitive values in Terraform?
Mark variables and outputs with sensitive = true to keep them out of plan and log output. Note that this only suppresses display — the value is still stored in plain text inside the state file, so backend access controls and encryption at rest are the real protection layer.
Key facts
- Tarmac has 104 Terraform interview questions on this topic, 25 of them on this page, at difficulty 1–5 of 5.
- Tarmac tracked 1,992 job postings asking for Terraform in August 2026.
- Roles asking for Terraform advertise a median base salary of US$170,000, across 423 job postings as of August 2026.
- Tarmac last reviewed these Terraform interview questions on 31 August 2026.
At a glance
| Questions | 25 shown · 104 in the bank |
|---|---|
| Difficulty | 1–5 of 5 |
| Formats | Multiple answer, Fill in the blank, Flashcard, Multiple choice, Short answer, Code output, True / false, Ordering, Find the bug, Design exercise |
What you'll review
- dry modules
- providers
- version pinning
- resources
- state file
- plan apply
- secrets management
- hcl syntax
- count for each
- lifecycle meta
- remote state
- import
- module composition
Practice questions
Try one before you open the answer. Pick an option and press Check; it's marked on the spot.
Terraform/tf-best-practices/dry-modules
Which of the following practices help keep a Terraform module DRY (Don't Repeat Yourself)?#
Options
Pick every one that applies.
Show answer
DRY Terraform modules are achieved by defining reusable input variables with sensible defaults, extracting repeated resource blocks into a shared module sourced by multiple configurations, and parameterizing values like region or instance type instead of hard-coding them. Copy-and-pasting resource blocks across environments is the opposite of DRY.
DRY in Terraform means defining inputs once (variables with defaults), extracting shared logic into a reusable module, and parameterizing values instead of hard-coding them. Copy-and-pasting resource blocks across environments is the opposite of DRY because each copy must be maintained independently and drifts over time.
Terraform/tf-core/providers
The command you run first in a new or freshly-cloned configuration to initialize the working directory and download providers is terraform _____.#
Show answer
The command you run first in a new or freshly-cloned configuration to initialize the working directory and download providers is terraform **init**.
terraform init initializes the working directory: it downloads the required provider plugins and modules and configures the backend, writing the .terraform directory and the dependency lock file. It must run before plan or apply, and it's safe to re-run when you add a provider, change a module source, or reconfigure the backend.
Terraform/tf-best-practices/dry-modules
A team wants to provision identical infrastructure across dev, staging, and prod with only small per-environment differences. Which statements correctly describe the DRY benefit of writing one parameterized module and calling it three times with different tfvars files?#
Options
Pick every one that applies.
Show answer
Writing one parameterized Terraform module and calling it with different tfvars files per environment keeps code DRY because the same module source is reused with different input values, and a bug fix applied to the module benefits all environments. Modules do not replace remote backends or give teams separate editable copies.
A single parameterized module called with different variable values keeps the definition DRY: the same source is reused and a fix applied once propagates to every caller. Modules do not give each environment its own editable copy (that would re-introduce duplication), nor do they replace the need for a remote backend — state storage is a separate concern from code reuse.
Terraform/tf-best-practices/version-pinning
To pin the Terraform CLI version used for a configuration, you set the _____ argument inside the terraform {} block — for example:#
Show answer
To pin the Terraform CLI version used for a configuration, you set the **required_version** argument inside the terraform {} block — for example:
terraform { required_version = ">= 1.5.0, < 2.0.0" }
This tells Terraform to fail fast if the running CLI version does not satisfy the constraint.
The required_version argument lives inside the top-level terraform {} block and accepts a version constraint string. If the installed Terraform CLI does not match, Terraform exits with an error before applying any changes.
Terraform/tf-best-practices/version-pinning
In a Terraform version constraint, the pessimistic constraint operator _____ allows updates within the same minor version band. For instance, _____ 3.0 permits any release from 3.0.0 up to (but not including) 4.0.0.#
Show answer
In a Terraform version constraint, the pessimistic constraint operator **~>** allows updates within the same minor version band. For instance, **~>** 3.0 permits any release from 3.0.0 up to (but not including) 4.0.0.
The ~> operator is Terraform's pessimistic constraint operator. When given ~> 3.0, it resolves to >= 3.0.0, < 4.0.0; when given ~> 3.0.1, it resolves to >= 3.0.1, < 3.1.0, allowing only patch-level updates within the specified minor version.
Terraform/tf-core/resources
In a Terraform resource block, what are the two labels that must follow the resource keyword?#
Show answer
The resource type (e.g., aws_instance) and the resource name (e.g., web). Together they form the unique resource address, e.g., aws_instance.web.
Every Terraform resource block starts with the resource keyword followed by exactly two string labels: the resource type (which maps to a provider resource) and a local name you choose. The combination of these two labels forms the resource's address, which is how you reference it elsewhere in the configuration.
Terraform/tf-state/state-file
What is the primary purpose of the Terraform state file (terraform.tfstate)?#
Options
Show answer
The state file maps each resource address in your configuration to the real infrastructure object Terraform created (an instance id, a bucket ARN). Terraform reads it to compute the diff between desired and actual state, so it knows what to create, update, or destroy. It is not where you declare desired infrastructure (that's your .tf files), and it is not a credential or plugin cache.
State is Terraform's record of which real object (an AWS instance id, a bucket ARN) corresponds to each resource address in your config. Without it Terraform couldn't tell what already exists, so it couldn't compute a diff or know what to update or destroy. Credentials are supplied by the provider config/environment, the desired state is your .tf files, and plugins are cached under .terraform/. Losing or corrupting state is what makes Terraform try to recreate or orphan live infrastructure.
Terraform/tf-lifecycle/plan-apply
Explain the difference between terraform plan and terraform apply, and why running plan first matters in a team workflow.#
Show answer
terraform plan is a dry run: Terraform refreshes state, compares the desired configuration against the real infrastructure, and prints the set of changes (create/update/destroy) it would make, without changing anything. terraform apply actually executes changes to reach the desired state; by default it shows the same plan and asks for confirmation before applying. Running plan first matters because it's the review step — you (and reviewers in a PR) can see exactly what will be created, replaced, or destroyed before any real resource is touched, catching accidental destroys or replacements. Teams often save a plan (-out) and apply that exact plan so what's reviewed is what's executed.
The crisp split: plan = preview/dry-run that mutates nothing, apply = execute the changes. The workplace value of plan is that it's the safety/review gate — surfacing destroys and replacements before they happen, ideally posted on a PR. Saving the plan with -out and applying that file guarantees the reviewed changes are exactly the executed ones, which is the basis of safe CI-driven Terraform.
Terraform/tf-core/providers
What is a provider in Terraform?#
Show answer
A provider is a plugin that lets Terraform manage a specific platform's API — AWS, Azure, GCP, Kubernetes, Cloudflare, and so on. It defines the resource types and data sources you can use and translates Terraform's create/read/update/delete operations into that platform's API calls. You declare which providers and versions you need in a required_providers block, and terraform init downloads them. Without a provider, Terraform has no resources to manage.
Providers are how Terraform stays platform-agnostic at its core while managing almost anything: each provider plugin supplies the resource/data-source schemas and does the API translation. Knowing that providers are versioned plugins downloaded at init explains why required_providers and the lock file matter for reproducible runs.
Terraform/tf-best-practices/secrets-management
In Terraform, why should you never hard-code secrets (like API keys or passwords) directly as variable defaults in your .tf files, and what are two recommended approaches for passing secret values safely?#
Show answer
Hard-coding secrets in .tf files exposes them in version control, and Terraform state files store all attribute values — including secrets — in plaintext. The two recommended approaches for passing secret values safely are: (1) inject them at runtime via environment variables (TF_VAR_*), and (2) retrieve them from a dedicated secrets manager (e.g., AWS Secrets Manager, HashiCorp Vault). Separately, mark sensitive outputs with sensitive = true to suppress console display, and store state in a secure, encrypted backend — but note that sensitive = true only hides values from the console; it does NOT protect secrets in the state file.
Terraform state files store all attribute values, including secrets, in plaintext by default. Hard-coding secrets in source means they are visible in version control and in state. The two recommended approaches for passing secret values safely are injecting them at runtime via environment variables (TF_VAR_*) or retrieving them from a dedicated secrets manager (e.g., AWS Secrets Manager, HashiCorp Vault). The sensitive = true argument only suppresses console output — it does not encrypt or remove secrets from the state file, so it is a display safeguard, not a method for safely passing secret values. State backends should additionally use encryption and access controls.
Terraform/tf-lifecycle/plan-apply
In a terraform plan, a resource is shown with the symbol -/+ and the note # forces replacement. What is Terraform telling you?#
Options
Show answer
-/+ means Terraform will destroy the existing resource and then create a new one, because a changed attribute is immutable and cannot be updated in place; # forces replacement flags that attribute. An in-place update shows ~, and +/- (create-then-destroy) is the order produced by create_before_destroy. Spotting -/+ on a stateful resource is the warning that an edit will be destructive.
-/+ means destroy then create: an attribute that the provider cannot change on a live object (an EC2 AMI, a name that's immutable) was modified, so Terraform must replace the resource, and # forces replacement marks the offending attribute. ~ would be an in-place update; +/- (create then destroy) is the order you get when create_before_destroy is set, to avoid downtime. Misreading -/+ is how engineers accidentally schedule a destructive replacement of a stateful resource during what looked like a small edit.
Terraform/tf-core/hcl-syntax
Given this locals block, what is the resulting value of local.tags (evaluated with Terraform's merge function)?#
locals {
defaults = { env = "dev", team = "core" }
overrides = { env = "prod" }
tags = merge(local.defaults, local.overrides)
}Options
Show answer
`{ env = "prod", team = "core" }`
merge combines maps left to right, and when the same key appears more than once the value from the later argument wins. So env from overrides (prod) overrides env from defaults (dev), while team is carried through unchanged. Duplicate keys across arguments are not an error — last-wins is the whole point. This precedence is the standard pattern for a base tag/config map overridden per environment; getting the argument order backwards silently applies the wrong values.
Terraform/tf-best-practices/dry-modules
In a Terraform module, using for_each on a single resource block to instantiate multiple resources from a map of configurations is considered a DRY best practice compared to writing one separate resource block per instance, because it eliminates duplicated block structure and keeps the configuration as a single source of truth.#
Options
Show answer
True. Using for_each on a single resource block to create multiple similar resources from a map is the standard DRY pattern in Terraform. It eliminates duplicated block structure, prevents copy-paste drift, and lets you manage all instances through one map, which is exactly the kind of single-source-of-truth design the Terraform style guides recommend.
When several resources share the same type and argument structure and differ only by key-specific values, a single resource block with for_each iterating over a map is the idiomatic DRY approach. It removes repetitive block definitions, reduces the risk of copy-paste drift, and lets you add or remove instances by editing the map alone. This is explicitly recommended in Terraform's module-composition guidance.
Terraform/tf-best-practices/secrets-management
Order the flow of a secret through the Terraform workflow, from its origin to its deployment on a target infrastructure resource. Place the stages in strict dependency order — each stage cannot begin until the previous one has completed.#
Put these in order
Show answer
The correct order is a → b → d → c → e: the secret originates in a secrets manager, Terraform reads it via a data source, passes it to the provider during apply (which calls the cloud API), records the result (marked sensitive) in state only after the API call succeeds, and the target resource then stores and uses it at runtime. Terraform writes state after the provider API call, not before.
The secret must first exist in the secrets manager (a) before anything can read it. During plan or apply, Terraform's data source fetches the value from the manager's API (b). During the apply step, Terraform passes that value to the provider, which calls the cloud API to create or update the target resource (d). Only after the provider API call succeeds does Terraform record the result — including the secret, flagged sensitive so it is redacted in output — in its state file (c). At that point the target resource exists and holds and uses the secret at runtime (e). Each stage has a hard dependency on the previous one: the state write happens after the provider API call, not before.
Terraform/tf-lifecycle/count-for-each
You manage a set of resources from a list using count, and you remove an element from the middle of that list. What does the next terraform plan show?#
Options
Show answer
Because count addresses instances by numeric index, removing a middle element shifts every later element's index down by one. Terraform then sees those shifted positions as different objects and plans to destroy and recreate them — not just the removed one. for_each avoids this by keying instances on a stable string, so removing one key only destroys that key's resource. Prefer for_each for collections whose membership changes.
count addresses instances by position ([0], [1], [2]…). Removing a middle element shifts every later element down one index, so Terraform sees each shifted position as pointing at a different object and plans to destroy/recreate them. for_each instead keys instances by a stable string (["web"]), so removing one key only destroys that one. This is the central reason to prefer for_each for any collection whose membership changes over time — count causes avoidable churn and downtime.
Terraform/tf-lifecycle/lifecycle-meta
Which of the following are valid arguments inside a resource's lifecycle {} block? Select all that apply.#
Options
Pick every one that applies.
Show answer
The lifecycle block accepts create_before_destroy (reverse the replace order to avoid downtime), prevent_destroy (reject plans that would destroy the resource), and ignore_changes (ignore drift on listed attributes), along with replace_triggered_by. depends_on is also a meta-argument but belongs at the resource level, not inside lifecycle. Placing it inside lifecycle is a frequent mistake.
create_before_destroy, prevent_destroy, and ignore_changes (plus replace_triggered_by) are the lifecycle meta-arguments: they tune replacement order, block destroys, and tell Terraform to ignore drift on chosen attributes. depends_on is a meta-argument too, but it sits at the resource level, not inside lifecycle — putting it there is a common authoring error. Knowing what lifecycle controls is what lets you do a zero-downtime replacement or stop an external autoscaler's changes from showing up as perpetual drift.
Terraform/tf-best-practices/secrets-management
Marking an output with sensitive = true encrypts that value so it is no longer stored in plaintext in the state file.#
Options
Show answer
False. sensitive = true only hides a value from CLI output, showing <sensitive> in plan and apply logs; the value is still stored in plaintext in the state file, and terraform output -json or -raw reveals it. Keeping a value out of state requires an ephemeral output. Real protection comes from encrypting the backend and restricting access to state, not from the sensitive flag.
False. sensitive = true only redacts the value from CLI plan/apply output (it shows as <sensitive>); the value is still written to state in plaintext, and terraform output -json/-raw will print it. Anyone who can read the state file can read the secret. To keep a value out of state entirely you use an ephemeral output (Terraform 1.10+). Believing sensitive encrypts state is a dangerous assumption — protect state with backend encryption and access controls instead.
Terraform/tf-core/resources
This configuration fails with a Self-referential block / cycle error during terraform plan. Which line is the cause?#
1| resource "aws_security_group" "web" {
2| name = "web-sg"
3| ingress {
4| from_port = 443
5| to_port = 443
6| protocol = "tcp"
7| security_groups = [aws_security_group.web.id]
8| }
9| }Options
Show answer
Line 7 — the resource references its own .id in its arguments, which Terraform cannot resolve because the id isn't known until the resource is created
Line 7 makes the resource depend on its own computed id, which doesn't exist until the resource is created — a dependency cycle Terraform's graph rejects. To allow a security group to reference itself you set self = true on the rule instead of listing its own id. name is settable, inline ingress blocks are valid (a separate aws_security_group_rule is just an alternative), and protocol = "tcp" is correct. Self-reference cycles are a classic Terraform graph error that the message can make cryptic.
Terraform/tf-state/remote-state
Your team currently keeps terraform.tfstate on each engineer's laptop. Explain why you'd move to a remote backend (e.g. S3, Terraform Cloud) and what two problems it solves for a team.#
Show answer
A local state file means each engineer has their own copy, so the state diverges and is never authoritative; it also isn't backed up and can be lost or committed to git with secrets in it. A remote backend stores a single shared state in a central, durable location so the whole team plans and applies against the same source of truth. Crucially it enables state locking: while one apply holds the lock, others are blocked, preventing two people from writing state at once and corrupting it. It also gives durability/versioning (S3 versioning, backups) and keeps the (plaintext) state off laptops behind access controls and encryption at rest.
The two big wins are a single shared source of truth (no divergent laptop copies) and state locking to prevent concurrent applies from corrupting state. Strong answers also mention durability/versioning and that state holds secrets in plaintext, so a remote backend with encryption and access control is a security improvement too. This is the question that separates someone who has run Terraform solo from someone who has run it on a team.
Terraform/tf-workflow/import
Order the steps of the config-driven import workflow (Terraform 1.5+) to bring an existing, unmanaged resource under Terraform management.#
Put these in order
Show answer
Terraform's config-driven import (1.5+) adopts an existing resource through a fixed sequence. The correct order is: first write an import block giving the target resource address and the existing object's id, then run terraform plan -generate-config-out=generated.tf to produce matching configuration, then review and prune that generated config to the arguments you want to manage, then run terraform apply to import the object into state and reconcile it, and finally remove the now-satisfied import block. Being plannable is what makes it reviewable in a PR and safe in CI.
Config-driven import is declarative and plannable: you declare the import block, optionally let Terraform generate the resource config, prune that generation to the arguments you want, then apply to actually adopt the object into state. The import block is removed afterward since it has done its job. Unlike the old imperative terraform import command, this whole flow is visible in plan, reviewable in a PR, and safe to run in CI — which is why it's the modern way to adopt existing infrastructure.
Terraform/tf-modules/module-composition
Design the Terraform repository structure and state strategy for a service that must be deployed identically to dev, staging, and prod on AWS.#
Show answer
Module factoring. One shared module (modules/service) encodes the infrastructure shape. Each environment is its own root configuration (environments/dev, /staging, /prod) that calls the shared module and passes per-env inputs (instance size, replica count, domain) as variables. The shape is defined once; environments differ only in inputs, so they stay DRY without branching on var.env with conditionals.
State isolation. Each environment root has its own backend state — e.g. the same S3 bucket with a distinct key (dev/terraform.tfstate, prod/terraform.tfstate) or separate buckets/accounts. Because Terraform only ever sees one environment's state at a time, a dev plan/apply physically cannot reference or destroy prod objects. Blast radius is bounded by the state boundary.
Backend & locking. Remote backend (S3 with native lockfile or DynamoDB, or Terraform Cloud) with locking so concurrent applies serialize and can't corrupt state, plus encryption at rest. No local state for a team.
Secrets. No secrets in .tf or committed tfvars. Pull them from a secret manager (SSM/Secrets Manager) via data sources, or inject as CI/TF_VAR variables. Note that any value used still ends up in plaintext state, so state access control and encryption — not sensitive = true — are the real protection.
Workflow. CI runs terraform plan on every PR and posts it for review; on merge it applies the reviewed plan per environment, promoting dev → staging → prod. Providers and the shared module are version-pinned (and a committed lock file) for reproducibility.
Tradeoffs. Directory-per-environment is explicit and isolates state cleanly at the cost of some repetition in the thin root configs. Terraform workspaces keep one config + one backend and switch state by workspace — lighter, but it's dangerously easy to apply to the wrong workspace and they share backend config, so they suit ephemeral/per-developer copies more than long-lived dev/staging/prod. Full duplication of the module per env is the worst of both: lots of drift, no single source of truth.
The signal is whether the candidate isolates state per environment (the real safety boundary that stops dev from touching prod) and keeps things DRY via a shared module with per-env inputs — rather than duplicating config or branching on an env variable. Strong answers add a remote locking backend, secrets sourced outside source control with the caveat that state is plaintext, a plan-on-PR/apply-on-merge flow, and an honest comparison with workspaces (convenient but easy to misfire). This separates people who've operated multi-environment Terraform from those who've only run a single config.
Terraform/tf-best-practices/dry-modules
To follow DRY principles in Terraform, a single generic module with many optional input variables and complex conditional logic (count/for_each combined with numerous dynamic blocks to suppress unused resources) should replace multiple focused modules that each manage one resource type.#
Options
Show answer
FALSE. DRY in Terraform means reusing well-scoped, composable modules across environments — not collapsing everything into one heavily parameterized mega-module. A single generic module with complex count/dynamic logic to suppress unused resources is a recognized anti-pattern: it creates fragile code, obscures plan output, and widens the blast radius of every change. Focused modules with clear single responsibilities are preferred.
FALSE. DRY in Terraform does not mean collapsing every resource pattern into one mega-module. HashiCorp's module design guidance recommends keeping modules small, focused, and composable — each module should manage a single logical grouping of resources with a clear purpose. Over-parameterizing a module to handle many unrelated configurations introduces fragile conditional logic, makes terraform plan output harder to read, increases the blast radius of changes, and raises the learning curve for consumers. The correct application of DRY is reusing well-scoped modules across environments via versioned registries or module sources, not maximizing abstraction density inside a single module definition.
Terraform/tf-best-practices/version-pinning
You are the platform lead at a company with 15 engineering teams, each managing their own Terraform workspaces. Teams consume a set of internal shared modules published to a private Terraform registry. Currently, module source blocks reference the registry but many teams leave required_providers blocks unset or use unbounded >= constraints, leading to inconsistent provider behavior across workspaces.#
Show answer
Provider version constraints are declared in the root module of each workspace via the required_providers block. I recommend the pessimistic constraint operator ~> — for example, version = "~> 5.0" allows 5.x patches and minor releases but blocks 6.0. This gives teams automatic access to bug fixes and new resources within a major version while guaranteeing no breaking-change surprise. For providers with a history of regressions in minor releases, a tighter ~> 5.40 (patch-only) bound is appropriate.
Shared child modules also declare their own required_providers blocks, but with broader constraints — e.g., version = ">= 5.0". Terraform computes the intersection of the root module's constraint and every child module's constraint, so the effective version is the most restrictive set. This lets modules express their minimum compatibility without over-constraining consumers. Modules must never pin exact versions (e.g., = 5.40.2), because that would force every consumer into the same exact version and break the intersection logic when two modules disagree.
The upgrade workflow is version-controlled and gated:
- A team member opens a PR bumping the
required_providersconstraint (e.g., from~> 5.40to~> 5.41) and runsterraform init -upgradeto pull the new provider and regenerate.terraform.lock.hcl. - CI runs
terraform planagainst a non-production workspace; the plan diff is reviewed for unexpected resource changes that the provider upgrade might introduce. - Automated policy checks (OPA or Sentinel) validate that the new version is within the platform-approved range and that no deprecated resources are referenced.
- After merge, the same constraint is promoted to production workspaces following the team's normal apply cadence.
The .terraform.lock.hcl file is committed to every workspace repository so that every terraform init reproduces the exact provider version and checksum.
For drift detection, the platform team maintains a centralized scanner (a scheduled CI job or a tool like tf-summarize / a custom script) that clones every workspace repository, parses each .terraform.lock.hcl, and builds a dashboard showing the resolved provider version per workspace. Any workspace whose locked version falls outside the approved range (e.g., below the latest patch for a CVE) is flagged in a tracking issue. The platform team can then open automated PRs to bump the constraint and regenerate the lock file, or notify the owning team with a remediation SLA.
This design exercise tests a senior candidate's understanding of Terraform provider versioning: where constraints are declared (root vs child modules), how Terraform's constraint intersection works, the role of the lock file in reproducibility, and how to operationalize drift detection across many workspaces. A strong answer demonstrates that provider pinning is not just a syntactic choice but a governance problem requiring tooling and process.
Terraform/tf-best-practices/dry-modules
A DRY module receives a variable structured as a map of environments, where each environment maps to a list of subnet objects (each with env, name, and cidr fields). You need a single resource "aws_subnet" "this" block with for_each that creates one subnet per object across all environments, addressed by composite keys like "dev-app" or "prod-db".#
Show answer
Use flatten() to collapse the per-environment lists of objects into a single flat list, then a for expression to iterate that flat list and build a map keyed by a composite string. The full pattern is: for_each = { for s in flatten([for env, subnets in var.environments : [for sn in subnets : { env = env, name = sn.name, cidr = sn.cidr }]]) : "${s.env}-${s.name}" => s }. flatten() runs first (as the iterable inside the for expression), and the for expression produces the final map. Composite keys are necessary because two environments may define a subnet with the same name (e.g., both have "app"); a map cannot contain duplicate keys, so using only name would silently overwrite entries, causing Terraform to create fewer subnets than intended. The environment prefix guarantees uniqueness across the entire flattened set.
The nested structure (map → list → objects) cannot be passed directly to for_each because for_each requires a flat map or a set of strings. flatten() collapses the inner lists into one flat list of objects, and the for expression projects each object into a key-value pair in the resulting map. The key must be composite because Terraform maps cannot have duplicate keys — if two environments both define a subnet named app, only one would survive, leading to silent data loss and fewer resources than expected.
Terraform/tf-best-practices/secrets-management
You are auditing a Terraform configuration for a secrets-management regression. A sensitive variable db_password is declared with a non-empty default in its variable block, and the team sets the real secret through several mechanisms simultaneously. According to HashiCorp's documented variable-value precedence (Terraform 1.x), arrange the five sources below from LOWEST precedence (applied first, most easily overridden) to HIGHEST precedence (applied last, wins over all others).#
Put these in order
Show answer
From lowest to highest precedence, Terraform 1.x resolves variable values as: (1) the default in the variable block, (2) TF_VAR_* environment variables, (3) terraform.tfvars / terraform.tfvars.json, (4) *.auto.tfvars / *.auto.tfvars.json (alphabetical), and (5) -var / -var-file command-line flags. Each higher source shadows the one below it, so a committed tfvars file can override an env-var secret, and a -var flag overrides everything.
Terraform resolves a variable's value by checking sources in a fixed ascending precedence: first the default in the declaration (the floor), then TF_VAR_* environment variables, then terraform.tfvars/terraform.tfvars.json, then *.auto.tfvars/*.auto.tfvars.json files in alphabetical order, and finally -var/-var-file command-line flags which override everything below. A higher-precedence source that provides a value entirely shadows a lower one. For secrets, this means a stray default or a committed terraform.tfvars can silently override an environment-injected secret, while a -var flag will always win — making the precedence chain critical to audit when hardening secret injection.
Related interview questions
Job market
See terraform salaries and hiring demand from live job postings.
The other 79 questions
This page shows 25 and marks what you pick. That's as far as a page can go. A free account opens the other 79 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.
Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan