Terraform interview questions appear in DevOps, platform, cloud, and SRE loops wherever infrastructure is declared in code. Interviewers probe plan versus apply, state safety, module boundaries, count versus for_each, remote backends with locking, and how you recover from drift or a stale plan—not whether you can recite every HCL function.
Below are 30+ Terraform interview questions covering IaC architecture, HCL, state, modules, iteration, drift, CI/CD, testing, and troubleshooting. Pair this guide with the Terraform Associate certification course, what is Terraform, and lesson deep dives such as Terraform state, Terraform modules, and Terraform CI/CD.
Interview context and how to prepare
What Terraform interviews actually test
Terraform interviews check whether you can manage infrastructure safely with declarative code—versioned, reviewed, and applied with predictable blast radius.
| Area | What interviewers probe |
|---|---|
| IaC concepts | Declarative model, provider ecosystem |
| Workflow | init, fmt, validate, plan, apply |
| HCL | Resources, variables, outputs, expressions |
| State | Purpose, sensitivity, remote backends, locking |
| Modules | Reuse, composition, versioning |
| Iteration | count vs for_each trade-offs |
| Operations | drift, import, moved blocks, workspaces |
| Production | CI/CD gates, troubleshooting failed applies |
| Role | Emphasis |
|---|---|
| Junior DevOps | Basic resource, variable, plan/apply |
| Mid-level | Modules, remote state, CI plan on PR |
| Senior / platform | State migration, blast radius, policy-as-code |
A realistic 2–3 week preparation plan
| Week | Focus | Hands-on drill |
|---|---|---|
| 1 | HCL, providers, resources, variables | Terraform HCL syntax exercises on lab VM |
| 1 | State, plan, apply | Terraform plan and apply with local provider |
| 2 | Modules, count, for_each | Terraform modules and count vs for_each |
| 2 | Remote state, locking | Backend remote state lab |
| 3 | CI/CD, drift, import, mocks | Terraform CI/CD pipeline; drift refresh |
Follow the Terraform Associate certification course syllabus while you drill.
Beginner vs senior Terraform expectations
| Topic | Beginner | Advanced |
|---|---|---|
| Apply | terraform apply |
Saved plans, -target only in emergencies |
| State | "Terraform tracks resources" | Locking, migration, moved blocks |
| Modules | Copy-paste module call | Version pins, provider passing, testing |
| Iteration | count = 3 |
for_each maps, stable keys, destroy ordering |
| Secrets | Plain string in tfvars | Vault, CI secrets, sensitive outputs |
| Drift | "Someone changed the console" | refresh-only plans, policy enforcement |
Architecture and IaC
How do interviewers compare Terraform to Ansible, CloudFormation, or Pulumi?
What interviewers are testing: Whether you distinguish declarative infrastructure lifecycle management from procedural configuration or language-specific IaC—not tool logos.
| Tool | Model | Interview angle |
|---|---|---|
| Terraform | Declarative IaC, graph-based plan/apply | CRUD lifecycle for cloud APIs |
| Ansible | Procedural/convergent task execution | Config and app deploy on existing hosts |
| CloudFormation | AWS-native declarative stacks | AWS-only, deep service integration |
| Pulumi | General-purpose languages | Same IaC goals, different language surface |
A strong answer is:
"I use Terraform to create and wire cloud resources with a plan-first workflow. Ansible configures what already exists. Picking between them is about lifecycle management versus task orchestration—not which logo is on the slide."
What is Terraform in one practical sentence?
What interviewers are testing: Whether you describe the declarative plan/apply loop and how configuration, state, and provider refresh interact—not just "Terraform is IaC."
Terraform is an infrastructure as code tool that reads HCL configuration, builds a dependency graph, and drives provider APIs to reach the declared desired state. The core workflow is init → plan → apply with a persisted state file tracking real-world IDs.
See what is Terraform and how Terraform works for the full execution model.
A strong answer is:
"Terraform reads configuration, refreshes managed objects through providers, uses state to map resource addresses to real objects, then builds an execution plan to converge infrastructure toward the configuration."
Walk through terraform plan and apply safely.
What interviewers are testing: Whether you narrate fmt → validate → saved plan → review → apply and treat plan files as sensitive artifacts.
Safe workflow:
terraform fmt— format consistency (terraform fmt)terraform validate— static check (terraform validate)terraform plan -out=plan.tfplan— preview changes- Human or CI review — read add/change/destroy counts
terraform apply plan.tfplan— apply exact planned graph
Saved plan files can contain sensitive values and backend information, so keep them out of Git and protect them like Terraform state.
A strong answer is:
"I never apply blind. Plan to a file, review in CI or with a teammate, then apply that saved plan so what runs matches what we approved."
HCL, providers, and resources
What are the main HCL building blocks in a Terraform configuration?
What interviewers are testing: Whether you name the core configuration blocks—terraform, provider, resource, data, variable, output, locals—and what each contributes to a module.
| Block | Role |
|---|---|
terraform |
Terraform settings, required providers, optional backend/cloud configuration |
provider |
Explicit provider configuration when needed |
resource |
Managed infrastructure objects |
data |
Read existing/provider-supplied information |
variable |
Module inputs |
output |
Module/root outputs |
locals |
Internal reusable expressions (terraform locals) |
Example resource:
resource "local_file" "demo" {
filename = "${path.module}/hello.txt"
content = "interview prep"
}A strong answer is:
"Providers talk to APIs; resources are the objects I manage. Variables feed inputs, outputs expose results, and locals keep expressions readable without extra input prompts."
What is a Terraform provider?
What interviewers are testing: Whether you understand providers as separately versioned plugins, know where version constraints belong, and understand the role of .terraform.lock.hcl.
A provider is a plugin that implements resource types for one platform—AWS, Azure, Kubernetes, or hashicorp/local for files on disk.
terraform init downloads provider binaries listed in required_providers and records versions in the dependency lock file (provider version lock).
A strong answer is:
"I declare provider version constraints in required_providers and commit .terraform.lock.hcl, which records the selected provider versions and checksums. Normal init reuses those selections; init -upgrade deliberately reevaluates them."
When do you use a resource versus a data source?
What interviewers are testing: Whether you distinguish lifecycle management through a resource block from read-only lookup through a data block—the underlying object may still be managed elsewhere.
| Block | Purpose |
|---|---|
resource |
Terraform manages lifecycle through this block |
data |
Terraform reads information without managing that object's lifecycle through the data block |
Example: data.aws_ami looks up an AMI ID; resource.aws_instance creates the EC2 instance. See Terraform data sources.
A strong answer is:
"A resource block manages lifecycle; a data source only reads information. The underlying object may be managed elsewhere by Terraform, another system, or manually."
Variables and outputs
How do you pass variables into Terraform?
What interviewers are testing: Whether you know CLI variable precedence and keep secrets out of committed tfvars while considering state/plan persistence.
For Terraform CLI, variable sources apply in this order from lowest to highest precedence:
- Default in the
variableblock TF_VAR_*environment variablesterraform.tfvarsandterraform.tfvars.json*.auto.tfvarsand*.auto.tfvars.json(lexical order)-varand-var-fileoptions (processed in command-line order)
CLI -var and -var-file are the highest of these local sources. Full patterns: terraform variables.
variable "environment" {
type = string
description = "Deployment environment name"
default = "dev"
}A strong answer is:
"I keep secret values out of committed .tfvars files and supply them through the CI/HCP secret mechanism or another approved secret-injection path. For sensitive inputs I also check whether the value will persist in state or plans."
What are Terraform outputs used for?
What interviewers are testing: Whether you treat outputs as a module's public interface and know some values are plan-known while computed attributes may stay unknown until apply.
Outputs expose selected values from a module or root configuration. Some are known during planning; provider-computed values may remain unknown until apply—for humans, scripts, or remote state consumers.
output "web_address" {
value = "http://${aws_instance.web.public_ip}"
description = "Public URL of the web tier"
}Mark sensitive outputs when they contain secrets (terraform sensitive data). terraform_remote_state exposes root outputs, not arbitrary internal resource attributes. Full patterns in terraform output.
A strong answer is:
"Outputs form the public interface of a module or root configuration. Child-module outputs feed parent modules, automation can consume root outputs, and separate configurations can consume published values through remote state or another explicit data-sharing mechanism."
State and remote backends
Why does Terraform need state?
What interviewers are testing: Whether you explain state as the binding between configuration addresses and remote objects—not merely a cache for the dependency graph.
State maps HCL addresses (aws_instance.web) to real IDs (i-0abc123). Providers need those IDs for updates and destroys. State stores the binding between resource instances and remote objects along with metadata and last-known attribute values Terraform needs during planning and refresh.
Without state, Terraform would not know which remote object matches each resource block. Deep dive: Terraform state.
A strong answer is:
"State is Terraform's mapping between resource addresses in configuration and the real objects Terraform manages, including resources it created or imported. Plan compares config plus state to the provider's live view—missing state means Terraform may try to recreate or lose track of orphans."
Why must Terraform state be protected?
What interviewers are testing: Whether you treat remote state as confidential—credentials, private keys, and resource metadata require encryption and RBAC.
State can contain secrets even when variables or outputs are marked sensitive—sensitive = true primarily redacts display in normal CLI output; Terraform can still store the value in state and saved plans. Newer mechanisms such as ephemeral values and provider write-only arguments reduce what persists, but provider support and restrictions apply.
| Practice | Why |
|---|---|
| Remote state with appropriate encryption/access controls | Centralize and protect sensitive state |
| Backend-supported locking when available | Prevent concurrent state writes |
| Bucket/object versioning where supported | Recover from accidental state overwrite/deletion |
.gitignore local terraform.tfstate |
Prevent commits |
terraform state rm only with care |
Orphan real resources |
For S3 specifically, HashiCorp recommends enabling bucket versioning for state recovery. State commands: terraform state command. Sensitive handling: terraform sensitive data.
A strong answer is:
"I store shared state remotely with appropriate encryption and access controls, enable backend-supported locking where available, and never commit state to Git because it can contain full resource attributes and secrets."
How do remote state and locking work?
What interviewers are testing: Whether you know locking is backend-specific, can configure S3 native locking, and treat bucket versioning as a recovery layer.
A backend block configures where state lives—S3, GCS, Azure Blob, HCP Terraform, etc. For backends that support locking, Terraform automatically acquires a lock for operations that can write state; backend configuration determines how that lock is implemented.
Current S3 backend example with native state locking:
terraform {
backend "s3" {
bucket = "team-terraform-state"
key = "prod/network/terraform.tfstate"
region = "us-east-1"
use_lockfile = true
encrypt = true
}
}Older S3 backend configurations may use DynamoDB locking, but that mechanism is deprecated in current Terraform and will be removed in a future minor release.
Enable S3 bucket versioning as a recovery layer for accidental state deletion or overwrite.
Lesson: Terraform backend remote state and state locking.
A strong answer is:
"Remote state centralizes state, and I explicitly enable a backend-supported locking mechanism—for S3 today,
use_lockfile = true—so concurrent applies cannot both modify state."
Modules and iteration
Why use Terraform modules?
What interviewers are testing: Whether you justify modules for reuse, stable interfaces, and version pinning—not copy-paste HCL reduction alone.
Modules bundle reusable configuration trees—network, EKS cluster, database—with defined inputs and outputs. Root modules call child modules; child modules hide complexity.
module "vpc" {
source = "./modules/vpc"
cidr = var.vpc_cidr
}Pin registry modules with version constraints and pin VCS modules with an immutable tag or commit ref (terraform registry modules). Structure guide: terraform modules.
A strong answer is:
"Modules are reusable packages. I expose a small variable surface and outputs so every environment calls the same VPC module instead of forking copy-paste HCL."
How do you version a module from the Terraform Registry or Git?
What interviewers are testing: Whether you pin registry modules with version and Git modules with ref, and know .terraform.lock.hcl locks providers—not remote module selections.
Registry call:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.13.0"
}Git call pins a ref:
module "app" {
source = "git::https://example.com/infra.git//modules/app?ref=v2.1.0"
}The dependency lock file locks providers, not remote module versions—Terraform reselects modules from the source/version constraint on initialization, so module pinning matters independently.
See module source version for ref strategies.
A strong answer is:
"I pin registry modules with version = and Git modules with ref tags. Floating refs in production are how surprise schema changes land on Friday applies."
When do you use count versus for_each?
What interviewers are testing: Whether you pick for_each with stable string keys over count when resource identity must survive list reordering.
| Meta-argument | Key | Best for |
|---|---|---|
count |
Integer index 0..n-1 |
Simple replicas, optional resource (count = var.enable ? 1 : 0) |
for_each |
Map or set keys | Named instances, stable identities |
for_each keys must be stable—changing a key destroys and recreates. With count, instances are identified by numeric index. If those indices correspond to elements in an ordered list, inserting or removing an element can shift identities and cause unexpected replacements.
Full lesson: terraform count for_each.
A strong answer is:
"for_each when each instance has a meaningful name or key—subnets by region, users by email. count for homogeneous replicas or boolean optional resources. I avoid count on ordered lists that reorder."
Scenario: for_each destroyed the wrong resource after a key rename.
What interviewers are testing: Whether you understand for_each keys drive resource address identity and a rename triggers destroy/create.
for_each uses the key as the identity in state. Renaming "web" to "web-primary" makes Terraform plan destroy ["web"] and create ["web-primary"]—even if the cloud object could have been renamed in place.
Fix paths:
movedblock — tell Terraform the new address (moved removed block)terraform state mv— manual state address rename- Design keys on immutable IDs, not display names
A strong answer is:
"Keys are identities. I use stable IDs in for_each maps and moved blocks when I refactor addresses—otherwise Terraform rightly plans destroy/create."
Dependencies, lifecycle, and workspaces
How does Terraform decide resource order?
What interviewers are testing: Whether you rely on expression references for normal dependency edges and reserve depends_on for dependencies Terraform cannot infer.
Default order follows the dependency graph—if resource A references B's attributes, A depends on B. Explicit depends_on breaks rare ordering gaps when references are invisible.
Implicit reference:
resource "local_file" "child" {
content = local_file.parent.content
filename = "child.txt"
}Resource dependencies lesson: terraform resource dependencies.
A strong answer is:
"References build the graph automatically. I use depends_on only for a real dependency Terraform cannot infer from expression references—for example when one resource's behavior depends on another even though no attribute reference connects them. I do not use depends_on as a generic delay or eventual-consistency workaround."
What lifecycle meta-arguments do interviewers ask about?
What interviewers are testing: Whether you know when create_before_destroy, prevent_destroy, and ignore_changes apply—and that ignore_changes is not a drift-hiding crutch.
| Meta-argument | Effect |
|---|---|
create_before_destroy |
New resource first, then destroy old |
prevent_destroy |
Block destroy plans (guardrail) |
ignore_changes |
Ignore updates to selected attributes after creation when another system intentionally manages them |
replace_triggered_by |
Force replace when another resource changes |
Details: terraform lifecycle.
A strong answer is:
"create_before_destroy can reduce downtime when the platform permits old and new instances to coexist. prevent_destroy is a guardrail for critical resources, and ignore_changes is for attributes intentionally managed outside Terraform—not for hiding unexplained drift."
What are Terraform workspaces?
What interviewers are testing: Whether you distinguish CLI workspaces from HCP Terraform workspaces and know CLI workspaces are weak environment isolation.
Terraform CLI workspaces are multiple state instances inside one backend configuration—default, staging, prod—not separate cloud accounts by magic.
terraform workspace list
terraform workspace select stagingCLI workspaces are not HCP Terraform workspaces. HashiCorp distinguishes them: CLI workspaces are multiple state files attached to the same working directory and backend; HCP Terraform workspaces are broader execution boundaries with their own state, variables, and run settings.
Use CLI workspaces for ephemeral environments or small teams. CLI workspaces are not recommended as the isolation mechanism when environments require separate credentials or access controls—separate configurations or backends are usually clearer for that case. Lesson: terraform workspaces.
A strong answer is:
"Workspaces split state under the same backend config. I use them for quick sandboxes; for prod I often prefer separate state buckets or keys so blast radius and IAM differ per environment."
Import, moved blocks, and drift
How do you bring existing infrastructure under Terraform?
What interviewers are testing: Whether you know import binds addresses to existing objects, generated config is a starting point, and -generate-config-out remains experimental.
Two common paths:
| Approach | What it does |
|---|---|
terraform import CLI |
Imports into state; you write configuration yourself |
import {} block + plan |
Configuration-driven import; can generate starter HCL |
Configuration-driven import:
import {
to = aws_instance.web
id = "i-0abc123"
}Then plan with optional config generation:
terraform plan -generate-config-out=generated.tfImport associates an existing remote object with a Terraform resource address. With configuration-driven import, Terraform can also generate starter resource configuration using -generate-config-out, but you should review and simplify that generated HCL rather than treating it as production-ready configuration.
As of current Terraform documentation, configuration generation with -generate-config-out remains experimental—review generated HCL especially carefully and prune attributes rather than accepting every provider default.
Then terraform plan should show no changes once config matches reality. Guide: terraform import.
A strong answer is:
"Import attaches an address to an existing ID. I write or generate matching HCL, import, then plan until clean—generated config is a starting point, not something I apply blindly."
When do you use moved blocks?
What interviewers are testing: Whether you use moved blocks to preserve remote objects during address refactors when the move is valid.
moved blocks declare resource/module address changes so Terraform can update state bindings without destroying the underlying object where the move is valid.
Common cases:
- resource rename within a module
- move into or out of a module
count/for_eachaddress migrations- compatible type changes where Terraform and the provider support them
moved {
from = aws_instance.web
to = module.web.aws_instance.this
}Paired with terraform moved removed block lesson content.
A strong answer is:
"moved blocks are my refactor tool—rename modules or split resources while telling state the new address. I still verify with plan before apply."
What is Terraform drift and how do you handle it?
What interviewers are testing: Whether you can distinguish configuration, state, and live infrastructure and decide whether to revert drift or adopt it intentionally.
Drift means live infrastructure differs from state—someone edited the console, autoscaling changed capacity, or a failed apply left partial changes.
Responses:
| Approach | When |
|---|---|
terraform plan |
See drift as proposed changes |
terraform apply -refresh-only |
Update state to match reality without changing remote resources (drift refresh) |
| Revert console change | Policy says Terraform is source of truth |
| Update HCL | New desired state is intentional |
If the out-of-band change is intentional, update configuration to represent the new desired state and use refresh-only only when you deliberately need to synchronize state/output values. Do not treat refresh-only as a substitute for fixing HCL—refresh-only updates state but not configuration.
A strong answer is:
"Drift shows up in plan. I either apply Terraform's desired state back, or update HCL when the change is intentional—refresh-only syncs state to reality but does not make drift the new desired configuration."
Provider aliases, CI/CD, and troubleshooting
When do you use a provider alias?
What interviewers are testing: Whether you configure multiple provider instances with aliases and pass them into child modules via the providers map.
Aliases configure multiple instances of the same provider—multi-region AWS, separate AWS accounts, or prod versus DR Kubernetes clusters.
provider "aws" {
alias = "west"
region = "us-west-2"
}
resource "aws_s3_bucket" "backup" {
provider = aws.west
bucket = "logs-west"
}For child modules, pass required provider configurations through the module's providers map; the child declares expected aliases with configuration_aliases. Lesson: terraform provider alias module.
A strong answer is:
"Provider aliases let me configure the same provider for multiple regions or accounts. For child modules, I pass the required provider configurations through the module's providers map, and the child declares any expected aliases with configuration_aliases."
What Terraform gates belong in CI/CD?
What interviewers are testing: Whether you separate fmt/validate/plan gates from policy-as-code and static IaC security tools.
Typical pipeline:
terraform fmt -check— formattingterraform init -backend=falsethenterraform validate— configuration and provider schema validation without remote backendterraform planon PR — post diff to review- Policy-as-code — Sentinel, OPA, or your organization's policy system
- Static IaC security/lint tools — Checkov, tfsec, TFLint as applicable
- Apply only from protected branch with OIDC credentials—not long-lived keys
terraform validate checks configuration syntax and schema; it is not policy-as-code. For module tests, see terraform test in Q31. Full patterns: terraform CI/CD and terraform test.
A strong answer is:
"CI runs fmt, validate, and plan on every PR. Apply uses short-lived cloud credentials and only after human or policy approval—never auto-apply from laptop state."
Scenario: Error acquiring the state lock—what do you do?
What interviewers are testing: Whether you verify no active apply holds the lock before force-unlock and re-plan immediately after clearing a stale lock.
Ordered response:
- Confirm another pipeline or engineer is not legitimately applying
- Read lock info — lock ID, who, timestamp in error message
- Wait if active apply is running
terraform force-unlock LOCK_IDonly when lock is stale after crashed process- Re-plan immediately — the failed apply may have partially changed infrastructure; reconcile before retrying
Never force-unlock while someone else applies.
Locking reference: terraform state locking.
A strong answer is:
"I verify the lock is stale—a crashed CI job—not an in-flight apply. force-unlock is last resort with the lock ID, then I re-plan immediately to reconcile any partially completed changes."
Scenario: terraform init fails with provider plugin error.
What interviewers are testing: Whether you fix constraints or network/mirror issues and use init -upgrade intentionally—not hand-edit .terraform.lock.hcl.
Checklist:
- Network — can CI reach registry.terraform.io?
- Version constraint — typo in
required_providers - Lock file drift — inconsistent lock file after manual edits (inconsistent dependency lock file)
- Platform — wrong OS/arch binary in air-gapped env—use provider mirror
- Clear working cache — remove
.terraformand re-init; do not delete.terraform.lock.hclas routine troubleshooting
rm -rf .terraform && terraform initFor an intentional provider upgrade, use terraform init -upgrade within constraints and review the resulting lock-file diff. Init lesson: terraform init.
A strong answer is:
"I fix the provider source/version constraints or network/mirror issue, then rerun terraform init; for an intentional provider upgrade I use terraform init -upgrade and review the resulting lock-file diff."
What does known after apply mean in a plan?
What interviewers are testing: Whether you know computed attributes resolve during apply and downstream references still wire correctly.
Some attributes are unknown until the provider creates the resource—generated IDs, computed IPs. Plan shows (known after apply) for those values.
Downstream resources that reference them still work—Terraform wires dependencies and resolves values during apply. Lesson: terraform known after apply.
A strong answer is:
"(known after apply) means the provider computes that attribute during creation or update. Terraform can still pass the unknown value through dependency expressions and resolve it during apply."
Scenario: plan shows unexpected destroy of production resource.
What interviewers are testing: Whether you verify workspace, backend, and variables before debugging HCL—and never use -target to hide an unexpected destroy.
Stop—do not apply.
- Verify workspace / backend / state key — planning against the wrong environment is a common catastrophic mistake
- Verify variable files and credentials/account
- Read plan line — which address and why (attribute change, removed block, key change)
- Compare to last good plan in CI
- Check recent HCL edits—count index shift, for_each key rename, removed
lifecycle - Refresh-only plan if suspect stale state
- Revert commit or fix HCL; re-plan until destroy disappears
prevent_destroyon critical resources as guardrail
Do not use -target merely to hide an unexpected destroy from the plan; fix the root cause. HashiCorp treats targeting as an exceptional recovery/troubleshooting mechanism, not normal deployment workflow.
Troubleshooting library: terraform troubleshooting and terraform debug logging.
A strong answer is:
"Unexpected destroys are a full stop. I verify workspace and backend first, diff the plan against the last green build, find the HCL or state address change that triggered it, and re-plan until the destroy count is zero before anyone applies."
Operations, testing, and modern Terraform
What is the difference between terraform refresh, -refresh-only, and normal plan?
What interviewers are testing: Whether you distinguish normal plan refresh, refresh-only state sync, and deprecated terraform refresh.
| Mode | What it does |
|---|---|
Normal terraform plan |
Refreshes state from the provider before calculating changes (unless disabled), then compares configuration to refreshed state |
terraform apply -refresh-only |
Proposes state/output updates to match remote infrastructure without changing remote resources |
Legacy terraform refresh |
Older command that only updated state; prefer refresh-only plans in current workflows |
Accepting drift into state does not automatically update HCL. If someone manually changes a resource and you accept that into state with refresh-only, but configuration still requests the old value, a subsequent normal plan may attempt to restore the configured value.
Lesson: terraform drift refresh only.
A strong answer is:
"Normal plan refreshes then diffs config against state. Refresh-only updates state to match reality without applying changes—but my HCL still defines what Terraform will try to converge to on the next normal plan."
How do you force replacement of one resource?
What interviewers are testing: Whether you prefer -replace over deprecated taint so replacement intent appears in the plan.
Modern approach—replacement intent is visible in the plan:
terraform plan -replace='aws_instance.web'
terraform apply -replace='aws_instance.web'terraform taint still exists but is deprecated; -replace is preferred because the replacement shows in the plan instead of first mutating state.
A strong answer is:
"I use -replace so the destroy/create shows explicitly in plan review. taint is legacy—I avoid it in new workflows."
What is the difference between sensitive and ephemeral values?
What interviewers are testing: Whether you know sensitive controls display redaction while ephemeral controls persistence—and the concepts are orthogonal.
| Mechanism | Behavior |
|---|---|
sensitive |
Redacts value from normal CLI/UI display; can still be stored in state and saved plans |
ephemeral |
Makes an eligible value available during the operation but omits it from state and plan files |
| Write-only argument | Provider-defined resource argument whose value is not persisted; availability depends on provider support |
sensitive and ephemeral are orthogonal: sensitive controls display redaction; ephemeral controls persistence. A value can require both depending on context.
Terraform 1.10+ introduced ephemeral variables and child outputs; Terraform 1.11+ supports provider-defined write-only resource arguments.
Lesson: terraform sensitive data.
A strong answer is:
"sensitive hides secrets from routine output but not from state. ephemeral is for values that should not persist in artifacts at all—when the provider and Terraform version support it."
How do you test Terraform modules?
What interviewers are testing: Whether you know Terraform tests can execute plans or applies and when mock providers avoid creating real infrastructure.
| Tool | Purpose |
|---|---|
terraform validate |
Configuration syntax and provider schema |
terraform plan |
Preview changes for a particular run |
terraform test |
Run .tftest.hcl test files with assertions |
| External lint/security tools | Different purpose—style, policy, or static analysis |
terraform test executes test run blocks that can perform plans or applies. Tests using real providers can create real infrastructure that costs money—run them in isolated test accounts/projects and ensure cleanup; use mock providers when real infrastructure is unnecessary. It complements, not replaces, CI plan gates and policy engines.
Lesson: terraform test.
A strong answer is:
"terraform validate catches configuration/schema problems; terraform test verifies behavior with assertions. I use mock providers when provider calls are unnecessary and isolated real accounts when I need integration-level confidence."
When do you use locals versus input variables?
What interviewers are testing: Whether you keep variables as the module input surface and locals for internal derived expressions.
| Block | Role |
|---|---|
variable |
Input from caller, user, environment, or CI |
locals |
Internal computed or reused expression—not supplied externally |
variable "environment" {
type = string
}
variable "application" {
type = string
}
locals {
name_prefix = "${var.environment}-${var.application}"
}Use variables at module boundaries; use locals to avoid repeating expressions or building derived names inside a module. Lesson: terraform locals.
A strong answer is:
"Variables are the public input surface. Locals are private computed values inside the module—I do not expect callers to set them."
Summary
Terraform interviews reward candidates who can explain the declarative model clearly: configuration declares desired infrastructure, providers implement changes, and state binds addresses to real objects. Strong answers cover HCL building blocks, provider version constraints and lock files, expression-driven dependencies, and when depends_on or lifecycle meta-arguments are appropriate—not as defaults, but for real edge cases.
State and modules separate mid-level from senior loops. Interviewers expect remote backends with appropriate encryption, backend-supported locking, import and moved blocks for adoption and refactors, stable for_each keys, and module versioning that does not assume the lock file pins remote modules. Drift handling should distinguish reverting infrastructure, updating HCL, and refresh-only state sync.
Production signal shows up in CI gates, troubleshooting discipline, and modern Terraform features. Reviewed saved plans, workspace/backend verification before apply, re-plan after stale locks, -replace over taint, ephemeral and write-only values where supported, and terraform test with mocks or isolated accounts demonstrate you can operate Terraform safely—not only write resource blocks.

