91 Terraform Interview Questions and Answers (2026)

Terraform isn't a nice-to-have anymore, it's the default way teams manage infrastructure, and interviewers know it. As systems scale, the bar has risen: they want proof you understand state, plans, and modules in the real world, not just that you've run terraform apply once. Walk in shaky on how state works or how the plan graph resolves dependencies, and it shows fast.
This guide is 91 questions with concise, interview-ready answers, code where it helps, so you can actually explain your reasoning under pressure. It's worked from Junior to Mid to Senior, so you build from the fundamentals up to state backends, drift, and module composition. Work through it and you'll walk in ready to talk Terraform like you use it every day.
Q1.What is the Terraform state file, and why is it considered the source of truth even if the cloud provider has its own API?
The state file (terraform.tfstate) is Terraform's JSON record that maps the resources declared in your configuration to the real objects that exist in the provider. It is treated as the source of truth because it is the only place Terraform stores the binding between your code and provider resource IDs, plus metadata Terraform needs to plan changes.
What it stores:
Resource-to-ID mappings, attribute values, dependencies, and provider metadata.
Lets Terraform know a resource it manages already exists, so it doesn't recreate it.
Why not just query the cloud API:
The API can list resources, but it cannot tell you which ones Terraform owns or which config block created them.
State preserves that ownership link and caches attributes to compute diffs quickly.
Why it's the source of truth:
A plan is a three-way comparison: desired config vs. recorded state vs. real infrastructure (via refresh).
If state says a resource exists but it was deleted out-of-band, Terraform plans to recreate it: state, not the API, drives intent.
Practical implications:
State contains secrets in plaintext, so it must be secured.
Store it remotely (S3, Terraform Cloud) with locking to prevent concurrent corruption.
Q2.Explain the difference between a declarative tool like Terraform and an imperative tool like Ansible or shell scripts.
Declarative tools like Terraform describe the desired end state and let the engine figure out how to get there, while imperative tools like Ansible or shell scripts describe the steps to execute in order. The difference is "what" vs. "how."
Declarative (Terraform):
You declare the target: "3 servers exist." Terraform computes the delta between current and desired state.
Naturally idempotent: running twice with no config change produces no changes.
State-aware: it tracks what it manages and can detect drift.
Imperative (Ansible, shell):
You write the sequence of actions: create, configure, restart.
Idempotency must be engineered by hand (check-before-act), or reruns cause errors/duplication.
Great for procedural tasks: config management, app deployment, orchestration.
Nuance:
Ansible is somewhat hybrid: its modules aim for idempotency, but execution is still ordered and procedural.
Common pattern: Terraform provisions infrastructure, Ansible configures what runs on it.
Q3.Explain the difference between the 'Desired State' and the 'Current State' in the context of a Terraform run.
Desired state is what your configuration says should exist; current state is what actually exists (as recorded in the state file and refreshed from the provider). A Terraform run computes the difference between the two and produces a plan to reconcile them.
Desired state: Defined by your .tf files: the target configuration you want realized.
Current state:
The recorded state file, refreshed against the real infrastructure via the provider API.
Refresh detects drift: manual changes made outside Terraform.
Reconciliation:
desired == current: no-op.
desired has something current lacks: create.
current has something desired lacks: destroy.
both exist but attributes differ: update or replace.
Why it matters: This diff model is what makes Terraform idempotent and lets plan preview changes before apply.
Q4.What exactly happens during terraform init, and if you add a new provider to your configuration why do you have to run init again?
terraform init, and if you add a new provider to your configuration why do you have to run init again?terraform init prepares the working directory so Terraform can run: it configures the backend, installs providers and modules, and writes a dependency lock file. You must re-run it when you add a new provider because that plugin isn't yet downloaded or recorded in the lock file, so Core has no way to talk to it.
What init does:
Backend init: configures and connects to where state is stored (local, S3, Terraform Cloud), migrating state if needed.
Provider install: reads required_providers, downloads matching plugins from the registry into .terraform/providers.
Module install: fetches child modules referenced by source.
Lock file: records provider versions and checksums in .terraform.lock.hcl.
Why a new provider forces another init:
Its plugin binary hasn't been downloaded, so Core can't invoke it.
The lock file must be updated with the new provider's version and checksums.
Same applies when you add a new module or change backend config.
Good to know:
init is safe to run repeatedly (idempotent).
Use terraform init -upgrade to pull newer allowed provider versions.
Q5.What is the difference between terraform plan and terraform apply, and why is it a best practice to save a plan file?
terraform plan and terraform apply, and why is it a best practice to save a plan file?plan computes the difference between your configuration and real state and shows what it would do; apply actually executes those changes. Saving a plan file lets you guarantee that what you reviewed is exactly what runs.
terraform plan: Read-only: refreshes state, compares desired vs actual, and prints a create/update/destroy summary. It changes nothing.
terraform apply: Executes changes. Run bare, it computes a fresh plan and prompts for approval before applying.
Why save a plan file:
terraform plan -out=tfplan then terraform apply tfplan applies exactly the reviewed actions with no re-computation.
Prevents drift between review and execution: nothing new is introduced if state or config changed in between.
Ideal for CI/CD: plan is a reviewable artifact promoted to the apply stage.
Q6.What is the difference between terraform validate and terraform fmt?
terraform validate and terraform fmt?validate checks that your configuration is syntactically and semantically correct; fmt rewrites files to the canonical style. One checks correctness, the other checks (and fixes) formatting.
terraform validate:
Verifies syntax, references, argument types, and required attributes within the module.
Runs offline against config only: it does not contact providers or check real infrastructure.
Requires terraform init first (needs provider schemas).
terraform fmt:
Reformats .tf files to standard indentation and alignment: purely cosmetic.
Use terraform fmt -check in CI to fail on unformatted files without rewriting them.
Rule of thumb: fmt for style, validate for correctness. They are complementary, not alternatives.
Q7.What does the terraform console command do, and how is it useful during development?
terraform console command do, and how is it useful during development?terraform console opens an interactive REPL where you can evaluate expressions, functions, and references against your current configuration and state without running a plan or apply. It is a scratchpad for figuring out what an expression will actually produce.
What you can evaluate:
Built-in functions (join(), cidrsubnet(), lookup()) to test their output.
Resource and data attributes, var.*, local.*, and outputs read from state.
Complex expressions like for comprehensions and splat operators before committing them to config.
Why it helps development:
Fast feedback: debug a tricky interpolation without editing files and re-planning.
Read-only: it never modifies state or infrastructure, so it is safe to explore.
Q8.What is the risk of using -auto-approve, and in what contexts is it acceptable?
-auto-approve, and in what contexts is it acceptable?-auto-approve skips the interactive confirmation prompt, so Terraform applies (or destroys) immediately with no human reviewing the plan. The risk is that any unintended change, including a destroy, executes without a chance to stop it.
The core risk:
No last-line-of-defense: a bad variable, drift, or forced replacement is applied silently.
Especially dangerous with terraform destroy -auto-approve, which can wipe resources with no confirmation.
When it is acceptable:
Automated CI/CD where a saved, already-reviewed plan is applied (terraform apply tfplan): the review happened at the gate, not the prompt.
Ephemeral or throwaway environments (test sandboxes, CI-created infra torn down after).
Local dev on non-critical, easily recreated resources.
Rule of thumb: Never use it interactively against production; pair it only with a reviewed plan artifact and a controlled pipeline.
Q9.What is the conceptual difference between an input variable and a local value, and when is it a best practice to use a local?
local value, and when is it a best practice to use a local?An input variable is a parameter supplied from outside the module (by the caller, CLI, or files), while a local value is a named expression computed inside the module. Use a local to name and reuse a derived or repeated expression, not to receive external input.
Input variable = module interface:
Declared with variable, set by the caller, .tfvars, env vars, or CLI; it is the module's public API.
Can have a default, type constraint, and validation.
Local value = internal computed name:
Declared with locals and referenced as local.name; it cannot be overridden from outside.
Evaluated once and reused, so it's ideal for DRY expressions.
When to prefer a local:
You repeat the same expression (a computed name, tag map, or concatenation) in many places.
You want to give a complex expression a readable name for clarity.
The value is derived from other variables/resources and should NOT be user-settable.
Rule of thumb: variable for what callers should supply, local for what the module computes.
Q10.Explain the difference between output values and variable inputs.
Inputs and outputs are the two ends of a module's contract: input variables receive data into a module, while output values expose data out of it after apply. One parameterizes, the other publishes results.
Input variables:
Declared with variable; supplied by the caller before/at plan time.
Configure behavior (region, instance size, names).
Output values:
Declared with output; computed from resources and known after apply.
Expose results (an IP, ARN, endpoint) to the user or a parent module.
How they connect modules: A parent reads a child's output via module.child.output_name and can feed it into another module's input.
Direction: variables flow in, outputs flow out.
Q11.What is the purpose of the terraform output command, and how do you consume outputs programmatically?
terraform output command, and how do you consume outputs programmatically?The terraform output command reads output values from state and prints them, which is how you retrieve results (endpoints, IDs) after an apply. For automation you emit them as machine-readable JSON.
Interactive use:
terraform output lists all outputs; terraform output name prints one.
-raw prints a single string value with no quotes, handy for shell substitution.
Programmatic consumption:
-json emits a structured object you can parse (e.g. with jq) in CI/CD.
Other configs can read them remotely via the terraform_remote_state data source.
Note: outputs are read from state, so it reflects the last apply, not a fresh plan.
Q12.How does the ternary conditional expression work in HCL, and when would you use it?
HCL, and when would you use it?HCL's ternary is condition ? true_value : false_value: it evaluates a boolean and returns one of two values, giving you inline conditional logic without a full if/else block.
Syntax and evaluation:
The condition must be a bool; both result expressions should be the same type (Terraform converts them to a common type if possible).
Only the selected branch's value is returned, but both branches are type-checked.
Common uses:
Toggling resource attributes based on a variable (e.g. instance size by environment).
Providing a fallback default: var.name != "" ? var.name : "default"
Driving conditional creation with count: var.enabled ? 1 : 0
Caveat: For complex branching, nested ternaries get unreadable fast: prefer a lookup map or locals instead.
Q13.What are the major categories of Terraform's built-in functions, and can you give examples of when you'd use string, collection, or numeric functions?
Terraform ships a large library of built-in functions grouped by the kind of data they operate on: strings, collections, numeric, encoding, filesystem, date/time, hashing/crypto, IP network, and type conversion. There are no user-defined functions, so knowing the built-ins matters.
String functions: format(), join(), split(), replace(), lower(), substr(): e.g. build a resource name from parts.
Collection functions: length(), merge(), lookup(), keys(), flatten(), concat(): e.g. merge default tags with user tags.
Numeric functions: min(), max(), ceil(), floor(), abs(): e.g. compute a subnet count.
Other important categories:
Encoding: jsonencode(), base64encode().
Filesystem: file(), templatefile().
Type conversion, hashing, date/time, and IP/CIDR (cidrsubnet()).
Q14.What is a heredoc in HCL, and when would you use one over normal string interpolation?
HCL, and when would you use one over normal string interpolation?A heredoc is HCL's multi-line string syntax, letting you write a block of text (including newlines and quotes) without escaping. You use it when embedding multi-line content like scripts, JSON, or policy documents.
Syntax:
Starts with <<DELIM and ends with a line containing only DELIM.
The indented form <<-DELIM strips leading whitespace so you can indent it neatly.
Interpolation with ${...} still works inside a heredoc.
When to use over normal strings:
Multi-line content where inline quoting would be ugly (user-data scripts, inline JSON/YAML).
When readability matters more than a one-liner.
Caveat: For structured data, prefer jsonencode() or templatefile() over hand-written heredocs to avoid syntax errors.
Q15.What is the difference between a resource block and a data source block?
resource block and a data source block?A `resource` block creates and manages the lifecycle of infrastructure (Terraform owns it); a `data` block only reads existing information without creating or destroying anything.
`resource` block:
Declares desired state: Terraform will create, update, or destroy the object to match.
Recorded in state and subject to full CRUD lifecycle management.
`data` block:
Read-only: queries a provider for existing objects and exposes their attributes.
Refreshed each plan; never creates or deletes anything.
Rule of thumb: Use `resource` for things you own; use `data` to reference things owned elsewhere (another team, another config, or the console).
Q16.What are data sources, and how do they allow Terraform to interact with infrastructure it does not manage?
Data sources let Terraform query and read attributes of infrastructure that exists outside the current configuration, so you can reference real objects without managing their lifecycle.
How they work:
Declared with a `data` block; the provider performs a read (API lookup) during plan/apply.
Returned attributes are available as `data.<type>.<name>.<attr>` for other resources to consume.
Interacting with unmanaged infrastructure:
Reference resources created manually or by another team (e.g. a shared VPC or AMI).
Bridge separate Terraform states via `terraform_remote_state`.
Pull dynamic values (latest AMI, current account ID, availability zones) at plan time.
Important boundary: Read-only: Terraform never modifies or destroys what a data source reads, so it stays out of that object's lifecycle.
Q17.What is the purpose of the terraform {} settings block, and what do required_version and required_providers control?
terraform {} settings block, and what do required_version and required_providers control?The terraform {} block configures Terraform itself (not resources): it declares behavior Terraform needs to know before running, most importantly which Terraform version and which providers the configuration requires, plus backend/cloud settings.
Purpose of the block:
A settings container evaluated early: values must be literal constants (no variables or interpolation), because it configures the engine before the graph is built.
Holds required_version, required_providers, backend, and cloud blocks.
required_version:
Constrains the Terraform CLI version allowed to run this config (e.g. >= 1.5.0).
If the running CLI doesn't satisfy it, Terraform errors out immediately, preventing state corruption from an incompatible version.
required_providers:
Declares each provider's source address (e.g. hashicorp/aws) and version constraint.
The source is essential so Terraform knows the registry namespace, avoiding ambiguity between similarly named providers.
Q18.What is the difference between a 'Root Module' and a 'Child Module'?
The root module is the working directory where you run Terraform (the top-level .tf files); a child module is any module the root (or another module) calls via a module block. Every configuration has exactly one root module, and it may call zero or more child modules.
Root module:
The entry point Terraform loads from the current directory during plan/apply.
Typically defines providers, backend, and orchestrates child modules.
Child module:
A reusable package of resources invoked with module "name" { source = ... }.
Receives data through input variables and returns data through outputs; it does not configure providers itself (it inherits or is passed them).
Relationship: the same folder of code can be a root module when run directly, or a child module when referenced by another config: the role is about position, not the code itself.
Q19.What constitutes a 'Terraform Module,' and what are the benefits of using them in a large-scale project?
A Terraform module is simply a directory of .tf files grouped as a reusable unit, exposing input variables and outputs. Modules let large projects package infrastructure patterns once and reuse them consistently, which reduces duplication and enforces standards.
What constitutes a module:
Any folder containing configuration files is a module (even the root).
Its public interface is variable blocks (inputs) and output blocks (returned values); resources inside are the implementation.
Benefits at scale:
Reusability/DRY: define a VPC or service pattern once, instantiate across many environments.
Consistency and standards: encode tagging, naming, and security defaults so every team gets them.
Abstraction: consumers use a simple interface without knowing internal resource details.
Versioning: publish and pin module versions so upgrades are deliberate.
Maintainability: fixes in one module propagate to all callers on the next version bump.
Caveat: keep modules focused and composable rather than one giant "do-everything" module, which becomes rigid and hard to reuse.
Q20.What is the Terraform Registry, and what is the difference between the public and private versions?
The Terraform Registry is a catalog for sharing and versioning reusable modules and providers. The public registry hosts community and vendor content openly, while private registries scope internal modules to your organization behind access controls.
Public Registry:
Hosted at registry.terraform.io; anyone can browse and consume verified providers and community modules.
Referenced by a namespaced source like terraform-aws-modules/vpc/aws with a version constraint.
Private Registry:
Part of Terraform Cloud/Enterprise (or similar); publishes internal modules only to your org.
Adds access control, private versioning, and a curated internal catalog for reuse.
Common value: Both provide semantic versioning and pinning, so consumers control when they adopt a new module version.
Q21.What is a 'Remote Backend,' and what are the advantages of using one like S3 or Terraform Cloud over a local backend?
S3 or Terraform Cloud over a local backend?A remote backend stores Terraform state in a shared, remote location (like S3, Terraform Cloud, or a GCS bucket) instead of a terraform.tfstate file on your laptop, enabling team collaboration, locking, and safer storage.
Shared state for teams: Everyone reads/writes one authoritative state, so members don't clobber each other or work from stale copies.
State locking: Backends like S3 (with DynamoDB) or Terraform Cloud lock state during apply to prevent concurrent, corrupting writes.
Durability and versioning: Remote stores offer redundancy, encryption at rest, and version history to recover from mistakes: a lost laptop no longer means lost state.
Secrets stay off local disk: State often contains sensitive values; keeping it remote with access controls reduces exposure.
Extra features: Terraform Cloud adds remote runs, policy enforcement (Sentinel/OPA), and run history the local backend can't provide.
Q22.What do terraform state list and terraform state show do, and when would you reach for them?
terraform state list and terraform state show do, and when would you reach for them?Both are read-only inspection commands. terraform state list enumerates the resource addresses tracked in state, and terraform state show <address> prints the full attribute values of one tracked resource.
terraform state list:
Lists every resource address (e.g. aws_instance.web, module.db.aws_db_instance.main).
Accepts a filter to narrow results, useful for finding the exact address before a move or removal.
terraform state show:
Dumps the recorded attributes for a single resource as Terraform sees them in state.
Handy for confirming an ID, IP, or computed value without opening the raw JSON.
When to reach for them:
Debugging drift or unexpected plans, and finding addresses for state mv, state rm, or import work.
Note they show what's in state, not necessarily the live cloud resource.
Q23.What does the terraform login command do, and how does it relate to authenticating with Terraform Cloud?
terraform login command do, and how does it relate to authenticating with Terraform Cloud?terraform login runs an interactive browser-based OAuth flow to obtain an API token and stores it locally, so the CLI can authenticate to Terraform Cloud (or Enterprise) for remote state, remote runs, and the private registry.
What it does:
Opens a browser to app.terraform.io, has you approve a token, then saves it to ~/.terraform.d/credentials.tfrc.json.
You can target a custom host: terraform login <hostname> for Terraform Enterprise.
How it relates to authentication:
Any command using the cloud block or remote backend reuses that stored token automatically.
Equivalent alternatives: set TF_TOKEN_app_terraform_io or write the credentials file directly (common in CI where no browser exists).
Cleanup: terraform logout removes the stored credentials.
Q24.How does Terraform's provider plugin architecture work, and how does it let Terraform remain provider-agnostic?
Terraform Core is a small engine that knows nothing about any specific cloud; all API knowledge lives in providers, separate plugins that Core talks to over a defined RPC interface. Because Core only speaks this generic plugin protocol, it stays provider-agnostic and can manage anything a provider is written for.
Two layers:
Terraform Core: parses HCL, builds the dependency graph, computes the plan, manages state.
Providers: plugins that translate resource operations into actual API calls (e.g. aws, azurerm, google).
How they communicate:
Core launches each provider as a separate process and talks to it via gRPC (HashiCorp's go-plugin).
Each provider advertises a schema describing its resources and data sources; Core validates config against it.
The contract:
Providers implement standard operations: read (refresh), plan (diff), and apply (create/update/delete).
Core orchestrates ordering and state; the provider owns vendor-specific details.
Why this keeps Terraform agnostic:
Adding support for a new platform means writing a new provider, not changing Core.
Providers are versioned and distributed independently via the Terraform Registry.
Q25.Explain the Terraform lifecycle (Init, Plan, Apply, Destroy). What happens under the hood during each step?
The core lifecycle is initialize, preview, execute, and tear down. init prepares the working directory, plan computes what will change, apply makes it real, and destroy removes managed resources.
terraform init:
Downloads required providers and modules, and configures the backend where state lives.
Writes a lock file and sets up the .terraform directory.
terraform plan:
Refreshes current state from the provider, then diffs desired config against it.
Builds a dependency graph and outputs the create/update/destroy actions without changing anything.
terraform apply:
Executes the planned actions by calling provider APIs, walking the graph in dependency order (parallelizing where safe).
Updates the state file to reflect the new reality.
terraform destroy: Plans and executes deletion of all resources tracked in state, in reverse dependency order.
Throughout: State is locked during apply/destroy to prevent concurrent runs from corrupting it.
Q26.What is the difference between terraform apply and terraform apply with a planfile?
terraform apply and terraform apply with a planfile?Bare terraform apply computes a plan on the spot and asks you to confirm it; terraform apply <planfile> skips computation and prompting, executing a previously saved plan exactly as-is.
terraform apply (no planfile)
Q27.What does a terraform plan actually represent, and does a successful plan guarantee a successful apply: why or why not?
terraform plan actually represent, and does a successful plan guarantee a successful apply: why or why not?A terraform plan is a proposed set of changes: Terraform compares your desired configuration against the recorded state and the real infrastructure, then lists what it would create, update, or destroy. It is a point-in-time prediction, not a contract, so a clean plan does not guarantee a clean apply.
What a plan represents:
A diff of three things: your config (desired), the state file (last known), and (via refresh) the real provider API.
An ordered action list (create/update/replace/destroy) derived from the dependency graph.
Why apply can still fail:
Drift between plan and apply: someone changes infrastructure in the window between the two.
Provider/API errors at execution: quota limits, permission denials, name collisions, invalid combinations the provider only validates at create time.
Runtime dependencies: values not known until apply ((known after apply)) can surface conflicts.
How to make apply match the plan:
Save the plan (terraform plan -out=tfplan) and apply that exact file so apply reuses the reviewed decisions.
State locking prevents concurrent changes from invalidating the plan.
Q28.In a CI/CD pipeline, why is it important to run terraform plan and terraform apply in separate stages with a manual approval step?
terraform plan and terraform apply in separate stages with a manual approval step?Splitting plan and apply into separate stages with a manual gate gives a human the chance to review exactly what will change before any mutation happens, turning infrastructure changes into a reviewed, auditable decision instead of an automatic one.
Human review of destructive actions: The plan output makes replacements and destroys visible so a reviewer can catch an accidental resource deletion before it runs.
Safety and blast-radius control: A bad merge or variable change is caught at the gate, not after production is already altered.
Consistency between stages: Save the plan as an artifact (-out=tfplan) and feed it to terraform apply tfplan so apply executes precisely what was approved, not a freshly recomputed plan.
Auditability and separation of duties: The approval records who authorized the change and when, and lets a different person approve than the one who wrote it.
Q29.What does the terraform graph command produce, and how can it help you understand your configuration?
terraform graph command produce, and how can it help you understand your configuration?terraform graph outputs a DOT-format description of the dependency graph Terraform builds internally, showing how resources, data sources, providers, and modules depend on one another. Piped into Graphviz it becomes a visual map of your configuration's relationships.
What it produces:
DOT text describing nodes (resources, providers, variables) and directed edges (dependencies).
Render it with Graphviz: terraform graph | dot -Tsvg > graph.svg.
How it helps understanding:
Reveals ordering: the graph is what drives Terraform's create/destroy sequence and parallelism.
Exposes hidden or unexpected dependencies (often from implicit references) that make resources coupled.
Helps debug cycles or why a change forces something else to update.
Caveat: For large configurations the graph gets dense and hard to read, so it is most useful on focused modules.
Q30.How do you handle sensitive data like passwords or API keys that ends up in the plain-text state file?
You can't keep secrets out of state entirely: Terraform stores resource attributes (including passwords) as plain text in state. The real practice is to protect the state itself and avoid hardcoding secrets in code, sourcing them from a secrets manager instead.
Secure the state backend:
Use a remote backend with encryption at rest (e.g. S3 with SSE, Terraform Cloud).
Lock down access with IAM/RBAC and enable state locking; never commit state to Git.
Keep secrets out of source code:
Fetch them at apply time from Vault, AWS Secrets Manager, or SSM via data sources.
Pass via environment variables (TF_VAR_) or CI secrets, not committed .tfvars.
Mark values sensitive: Prevents accidental display in CLI/plan output, but does NOT encrypt them in state.
Key caveat: Anyone with read access to state can read the secrets, so treating state as sensitive is the primary control.
Q31.How does Terraform determine variable precedence if a value is defined in a .tfvars file, an environment variable, and a default value?
.tfvars file, an environment variable, and a default value?Terraform loads variable values from multiple sources and applies them in a defined order, where later sources override earlier ones. The default is the lowest priority, and explicit command-line flags win over everything.
Lowest: the default in the variable block.
Environment variables (TF_VAR_name).
The terraform.tfvars file, then terraform.tfvars.json.
Any *.auto.tfvars / *.auto.tfvars.json files, in lexical order.
Highest: -var and -var-file on the command line, in the order given.
So for your example: the .tfvars file beats the env var, which beats the default.
Within the same tier, the last value read wins (relevant for multiple -var-file flags).
Q32.What does marking a variable or output as 'sensitive' actually do, and what are its limitations?
sensitive' actually do, and what are its limitations?Marking something sensitive tells Terraform to redact it from CLI and plan output so it isn't accidentally displayed in logs or terminals. It's a display-level safeguard, not encryption.
What it does:
Replaces the value with (sensitive value) in plan, apply, and output.
Sensitivity is contagious: expressions derived from a sensitive value are also redacted.
An output fed a sensitive value must itself be marked sensitive or Terraform errors.
What it does NOT do:
Does not encrypt or hide the value in the state file (still plain text there).
Does not stop a determined user from extracting it (e.g. terraform output -json still returns the raw value).
Providers may still write it to their own logs if misconfigured.
Takeaway: it prevents accidental exposure, not deliberate access; secure the state for real protection.
Q33.What are for-expressions in HCL, and how do you use them to transform a list or map into another collection?
HCL, and how do you use them to transform a list or map into another collection?A for-expression iterates over a collection and builds a new one, letting you transform, filter, or reshape data. Bracket style decides the result type: [] produces a list/tuple, {} produces a map/object.
List form: [for x in var.list : upper(x)] transforms each element.
Map form: {for k, v in var.map : k => upper(v)} builds key/value pairs.
Filtering: Add an if clause to keep only matching elements.
Iterating a list with index: [for i, v in list : ...] exposes the index; for maps you get key and value.
Q34.What is a splat expression in Terraform, and what problem does it solve when working with lists of resources?
A splat expression is shorthand for extracting the same attribute from every element of a list, most often a list of resource instances created with count. It replaces a verbose for-expression for the common "give me all of attribute X" case.
Syntax:
aws_instance.web[*].id returns a list of every instance's id.
Equivalent to [for i in aws_instance.web : i.id].
Problem it solves: With count a resource becomes a list, and you often need all IDs/IPs for an output or another resource.
Handy behavior: Splat on a single (non-list) value wraps it in a one-element list, avoiding null issues.
Caveat: Splat works with count (lists), not for_each (maps); for maps use values() or a for-expression.
Q35.What are the different type constraints in Terraform (string, number, bool, list, map, set, object, tuple), and why do complex types matter?
string, number, bool, list, map, set, object, tuple), and why do complex types matter?Terraform types fall into primitives (string, number, bool), collections (list, map, set), and structural types (object, tuple). Complex types matter because they let you validate input shape and pass rich, structured data safely.
Primitive types: string (text), number (int or float), bool (true/false).
Collection types (all elements the same type):
list(T): ordered, allows duplicates, indexed by position.
map(T): key/value pairs with string keys.
set(T): unordered, no duplicates.
Structural types (elements can differ):
object({...}): named attributes each with their own type, like a struct.
tuple([...]): fixed-length sequence where each position has its own type.
Why complex types matter:
They enforce input contracts, so bad variable shapes fail at plan time instead of producing confusing errors.
They document intent and enable rich module interfaces (e.g. a list of subnet objects).
any opts out of checking; use it sparingly.
Q36.How does type conversion work in Terraform, and what functions help convert between types?
Terraform automatically converts between types when it safely can (implicit conversion), and provides explicit conversion functions when you need to force a type. Conversion is driven by the type constraint the value is being assigned to.
Implicit (automatic) conversion:
Terraform converts to satisfy a required type, e.g. the string "5" to number 5, or true to "true".
Only unambiguous conversions happen; otherwise you get an error.
Explicit conversion functions:
tostring(), tonumber(), tobool() for primitives.
tolist(), toset(), tomap() for collections (e.g. toset() to dedupe a list).
When it matters:
Converting a list to a set for for_each, which requires a set or map.
Coercing user input or data source output into the exact type a resource expects.
Q37.Why are provisioners like local-exec and remote-exec considered a last resort in Terraform? What are the recommended alternatives?
local-exec and remote-exec considered a last resort in Terraform? What are the recommended alternatives?Provisioners run imperative scripts as a side effect of resource creation, which breaks Terraform's declarative model: their results aren't tracked in state, they aren't idempotent, and failures leave resources tainted. HashiCorp explicitly calls them a last resort.
Why they're a last resort:
Not tracked in state: Terraform can't detect drift or reconcile what the script did.
Not idempotent: re-running may not be safe, and they only run at create/destroy time.
Fragile: a failed provisioner taints the resource, forcing recreation, and needs network/SSH/WinRM access.
Recommended alternatives:
Bake images ahead of time with Packer, so instances boot ready to go.
Use cloud-native bootstrapping like user_data / cloud-init for instance configuration.
Use dedicated config management (Ansible, Chef, Puppet) after provisioning.
Prefer a real provider or data source over shelling out with local-exec.
If you must use them: Keep them minimal, and know local-exec runs on the machine running Terraform while remote-exec runs on the target via SSH/WinRM.
Q38.Explain the terraform_data resource (formerly null_resource). When would you use it?
terraform_data resource (formerly null_resource). When would you use it?`terraform_data` is a built-in resource (added in Terraform 1.4) that replaces the null_resource provider: it holds state, supports lifecycle features like triggers and provisioners, but needs no external provider.
What it is:
A do-nothing resource that exists purely to participate in the graph, store data, and hang provisioners or replacement triggers off of.
Unlike `null_resource`, it ships with Terraform core, so no `hashicorp/null` provider download is required.
Key arguments:
`input`: an arbitrary value stored in state and exposed unchanged as `output`.
`triggers_replace`: when this value changes, the resource is replaced (useful to force `provisioner` re-runs).
When to use it:
To run provisioners not tied to a real resource.
To force downstream recreation when an arbitrary input changes.
As a passthrough to stash a computed value in state.
Q39.What is the difference between a creation-time and a destroy-time provisioner?
A creation-time provisioner runs when the resource is created (the default); a destroy-time provisioner runs just before the resource is destroyed, declared with `when = destroy`.
Creation-time (default):
Executes after the resource is created; if it fails, the resource is marked tainted and recreated next apply.
Typical use: bootstrap or configure a freshly created machine.
Destroy-time (`when = destroy`):
Runs before the resource is destroyed; a failure blocks the destroy.
Typical use: graceful cleanup, draining, or deregistration.
Constraint: can only reference `self`, not other resources or variables.
Q40.What is the difference between a provider and a provisioner, and why are provisioners like local-exec considered a last resort?
local-exec considered a last resort?A provider is a plugin that lets Terraform manage a platform's resources through its API; a provisioner runs scripts or commands on a resource (or locally) as a side effect during create/destroy. Provisioners like `local-exec` are a last resort because they fall outside Terraform's declarative state model.
Provider:
Declarative: exposes resource and data source types and reconciles them via APIs (e.g. `aws`, `azurerm`).
Fully tracked in state, planned, and idempotent.
Provisioner: Imperative escape hatch: `local-exec` runs on the machine running Terraform, `remote-exec` runs on the target.
Why last resort:
Not idempotent: Terraform can't plan or track what a script did.
Failures taint resources and are hard to recover from cleanly.
Prefer native resources, `cloud-init`/`user_data`, or config-management tools instead.
Q41.What does the templatefile function do, and when would you use it instead of inline strings or heredocs?
templatefile function do, and when would you use it instead of inline strings or heredocs?`templatefile` reads a file and renders it with variables you pass in, returning the interpolated string: it keeps large or reusable templates out of your `.tf` code.
Signature and behavior:
`templatefile(path, vars)` substitutes `${...}` interpolations and supports directives like `%{ for }` and `%{ if }`.
Evaluated during plan, so the rendered result is known before apply.
When to prefer it:
Large or multi-line content: cloud-init, `user_data`, config files, JSON/YAML.
Reuse across resources or loops with different variable sets.
Cleaner than a heredoc when the template has real logic or is long.
When inline/heredoc is fine: Short, one-off strings with a couple of interpolations don't justify a separate file.
Q42.Explain the purpose of the .terraform.lock.hcl file and why it should be committed to version control.
.terraform.lock.hcl file and why it should be committed to version control.The `.terraform.lock.hcl` file is the dependency lock file: it pins the exact provider versions and records their checksums so every run and every teammate uses identical provider binaries.
What it locks:
The selected version of each provider (within your constraints).
Cryptographic hashes for the provider packages, verified on `terraform init`.
Why commit it:
Reproducibility: CI and every developer get the same provider versions, avoiding "works on my machine" drift.
Security: checksum verification detects tampered or corrupted downloads.
Updating it:
Run `terraform init -upgrade` to bump versions, then commit the changed lock file.
Use `terraform providers lock` to add hashes for other platforms in team/CI setups.
Q43.How do you manage multiple instances of the same provider (e.g., deploying to two different AWS regions) using 'Aliases'?
Provider aliases let you configure the same provider multiple times (e.g. different regions or accounts) and route each resource to a specific configuration via the `provider` argument.
Define aliased providers: One block without `alias` is the default; each additional block sets `alias = "..."`.
Reference on resources/data: Set `provider = aws.<alias>` to pick a non-default configuration.
In modules: Pass aliases explicitly with a `providers = { aws = aws.west }` map in the module call.
Q44.How do provider version constraints work, and what does an operator like ~> mean in a version constraint?
~> mean in a version constraint?Version constraints tell Terraform which provider versions are acceptable; during terraform init it selects the newest version that satisfies all constraints and records it in the lock file. The ~> operator ("pessimistic") allows patch/minor upgrades but pins a ceiling.
How constraints resolve:
Multiple constraints (from root and modules) are combined; Terraform picks the highest version meeting all of them.
The chosen version is written to .terraform.lock.hcl so future inits are reproducible until you deliberately terraform init -upgrade.
Common operators:
= exact version; >=, <=, >, < for ranges.
!= excludes a specific bad version.
~> pessimistic: allows the rightmost component to increase only.
What ~> means concretely:
~> 5.0 allows >= 5.0, < 6.0 (any 5.x).
~> 5.2.0 allows >= 5.2.0, < 5.3.0 (patch only).
Popular because it lets you receive bug fixes while blocking breaking major releases.
Q45.How can you keep provider credentials out of your configuration, and what are the common ways Terraform authenticates to a cloud provider?
Keep credentials out of .tf files entirely: never hardcode keys in provider blocks (they'd land in version control and often in state). Instead let providers read credentials from the environment, shared config files, or ambient cloud identity so secrets stay outside the codebase.
Why not in config: Hardcoded secrets get committed to Git and can leak into plan output or state.
Common authentication methods:
Environment variables: AWS_ACCESS_KEY_ID, GOOGLE_CREDENTIALS, ARM_CLIENT_ID, etc.
Shared CLI config/profiles: ~/.aws/credentials via a named profile, gcloud ADC, or az login.
Ambient/instance identity: EC2 instance profiles, IRSA on EKS, GCP service account attached to the runner, Azure managed identity.
Short-lived credentials via OIDC: CI (GitHub Actions, Terraform Cloud) assumes a role and gets temporary tokens, the preferred modern approach.
If a secret must be passed in, use variables sourced from a secrets manager or TF_VAR_ env vars, never committed .tfvars.
Q46.Why is it critical to pin module versions in a production environment, and what are the pros and cons of using a Git URL versus a Terraform Registry source?
Pinning module versions guarantees that a given commit of your config always resolves to the exact same module code, so plans are reproducible and an upstream change can't silently alter or destroy production infrastructure. Registry sources support semantic version constraints; Git URLs pin by ref (tag/commit) and are more flexible but less standardized.
Why pinning is critical:
Without a pin, a new module release can introduce breaking resource changes that surface as unexpected destroy/create in a plan.
Reproducibility: the same config produces the same plan across machines, CI, and time.
Terraform Registry source:
Pros: supports the version argument with constraints (~>), clean namespaced addresses, discoverable and documented.
Cons: module must be published to a (public or private) registry.
Git URL source:
Pros: works with any repo, no registry needed, good for internal/private modules.
Cons: no version constraint operators; you must pin with ?ref= to a tag or commit SHA, and a branch ref is a moving target (unsafe for prod).
Q47.What makes a good module? Explain the trade-offs between creating highly generic modules versus specialized, small modules.
A good module has a clear single responsibility, a small well-documented interface, sane defaults, and useful outputs. The trade-off is between highly generic modules that cover many cases through many variables, and small specialized modules that do one thing well but proliferate.
Traits of a good module:
Focused scope, minimal required inputs, sensible defaults, and outputs that let callers wire it up.
Documented, versioned, and independently testable.
Highly generic modules:
Pro: one module reused everywhere, fewer things to maintain.
Con: variable explosion and conditional sprawl; hard to understand and easy to misuse.
Small specialized modules:
Pro: simple, readable, safe defaults for a known use case.
Con: more modules to track and compose; potential duplication across similar ones.
Practical guidance: Start specific and generalize only when a second real use case appears; resist speculative flexibility.
Q48.How does Terraform determine the order in which resources are created? Explain the difference between implicit and explicit dependencies.
Terraform builds a dependency graph from your configuration and creates resources in the order those dependencies require, doing independent work in parallel. Dependencies are either implicit (inferred from references between resources) or explicit (declared manually with depends_on).
Implicit dependencies:
Created automatically when one resource references another's attribute, e.g. subnet_id = aws_subnet.main.id.
Preferred: Terraform infers the correct order without you stating it.
Explicit dependencies:
Declared with depends_on when a dependency exists but isn't expressed through a reference (a hidden side effect like IAM propagation).
Use sparingly: overuse serializes the graph and slows applies.
Parallelism: Resources with no dependency between them are created concurrently up to the -parallelism limit.
Q49.What is the fundamental difference between using count and for_each to create multiple resources, and which one is safer for resources that might be removed from the middle of a list, and why?
count and for_each to create multiple resources, and which one is safer for resources that might be removed from the middle of a list, and why?Both create multiple instances of a resource, but count keys instances by numeric index while for_each keys them by a stable string key from a map or set. for_each is safer for lists where a middle element may be removed, because count would shift every subsequent index and cause destroy/recreate churn.
count:
Instances are addressed positionally: aws_instance.web[0], [1], etc.
Remove an item from the middle and everything after it renumbers, so Terraform destroys and recreates those resources.
Good for N identical copies where identity doesn't matter.
for_each:
Instances are addressed by key: aws_instance.web["api"].
Removing one key leaves the others' addresses untouched, so only that instance is destroyed.
Preferred whenever items have stable identities.
Bottom line: Use for_each for keyed, mutable collections; reserve count for simple on/off or fixed-N cases.
Q50.What is a dynamic block, and when would you use one instead of simply defining multiple standard resource blocks?
dynamic block, and when would you use one instead of simply defining multiple standard resource blocks?A dynamic block generates repeatable nested configuration blocks inside a resource at plan time, iterating over a collection. You use it when a resource has a nested block that repeats (like ingress rules) and the number or content is data-driven, rather than hand-writing each nested block.
What it does:
Iterates a map or list and emits one nested block per element, with values read from each.value.
Applies to nested blocks within a resource, not to top-level resources (that's for_each on the resource itself).
When to use it: The nested block set is variable or comes from input variables (e.g. security group rules, tags, settings blocks).
When not to use it: If there are just a couple of fixed blocks, write them literally: dynamic blocks hurt readability, so don't over-abstract.
Q51.Can you use count and for_each on module blocks, and how does that change how you call modules?
count and for_each on module blocks, and how does that change how you call modules?Yes, since Terraform 0.13 both count and for_each work on module blocks, turning a single module call into multiple instances that you address by index or key.
count creates a numbered list:
Instances become module.name[0], module.name[1], etc.
Best for identical copies where order is stable; removing a middle item shifts indexes and can cause churn.
for_each creates a keyed map:
Instances become module.name["key"], addressed by a stable string key.
Preferred when items are distinct: adding or removing one key doesn't disturb the others.
How it changes references:
Module outputs become a collection, so you index them: module.vpc["prod"].vpc_id.
Inside for_each you access each.key and each.value.
Q52.Explain the use cases for create_before_destroy, prevent_destroy, and ignore_changes. How does create_before_destroy help with zero-downtime updates?
create_before_destroy, prevent_destroy, and ignore_changes. How does create_before_destroy help with zero-downtime updates?These are lifecycle meta-arguments that override Terraform's default create/update/destroy behavior for a resource.
create_before_destroy:
When a change forces replacement, Terraform makes the new resource first, then destroys the old one.
Enables zero-downtime updates: the replacement is up and healthy (and can be swapped into a load balancer) before the old instance is torn down, so there's no gap in service.
prevent_destroy:
Causes Terraform to error out on any plan that would destroy the resource.
A safety guardrail for critical stateful resources (databases, prod buckets).
ignore_changes:
Tells Terraform to ignore drift on specific attributes after creation.
Useful when something external mutates a value (autoscaling changes desired count, a tag added by another system) and you don't want Terraform reverting it.
Q53.How do you rename a resource or move it into a module without Terraform attempting to destroy and recreate it, and why is a moved block preferred over terraform state mv?
moved block preferred over terraform state mv?Use a moved block: it tells Terraform the resource's address changed so it updates the state binding instead of destroying the old address and creating the new one.
Why an address change would otherwise destroy/recreate: Terraform tracks resources by address; a rename or move-into-module looks like the old one disappeared and a new one appeared.
The moved block: Declared in config with from and to addresses; Terraform remaps the existing state entry during the next plan.
Why it's preferred over terraform state mv:
It's declarative and committed to version control, so the refactor is reviewable and reproducible.
It runs automatically for everyone who applies, including CI, without a manual out-of-band command.
terraform state mv is imperative, run once by one person, and easy to forget or apply inconsistently.
Q54.What is the difference between the deprecated terraform taint command and the -replace flag in terraform apply?
terraform taint command and the -replace flag in terraform apply?Both force a resource to be replaced on the next apply, but terraform taint mutated state immediately and separately, while -replace is a plan-time flag that shows the replacement before you approve it.
terraform taint (deprecated):
Marked a resource as tainted in state right away, so the next plan would replace it.
Modified state as a side effect, with no plan preview at the moment you ran it.
-replace flag:
Passed to terraform plan or apply: terraform apply -replace="aws_instance.web".
State is only changed after you review and approve the plan, which is safer and clearer.
Takeaway: -replace is the recommended replacement because it keeps the plan/approve workflow intact.
Q55.What is state locking, and why is it required in a team environment? Which backends support it, and what happens if a lock is not released?
State locking prevents two operations from writing to the same state file at once by acquiring an exclusive lock before any run that could modify state. It's essential in teams because concurrent writes can corrupt state or cause lost updates.
Why it's required:
If two people run apply simultaneously, they can overwrite each other's state and desync it from real infrastructure.
Locking serializes state-modifying operations.
Backends that support it:
S3 (with a DynamoDB lock table or native S3 lockfile support), Terraform Cloud/Enterprise, Consul, GCS, azurerm, and others.
The local backend does not lock across machines.
If a lock isn't released:
A crashed or interrupted run can leave a stale lock, blocking further operations.
You clear it with terraform force-unlock <LOCK_ID>, but only after confirming no operation is actually running.
Q56.What is the difference between terraform state rm and terraform destroy?
terraform state rm and terraform destroy?terraform state rm only removes a resource from Terraform's tracking (state) and leaves the real infrastructure running, while terraform destroy actually deletes the real infrastructure.
terraform state rm:
Terraform forgets the resource: it stops managing it, but the cloud object still exists.
Used to hand a resource to another state, stop managing something, or fix state after a manual change.
terraform destroy:
Makes real API calls to delete the resources, then updates state to reflect they're gone.
Destructive and irreversible against live infrastructure.
Rule of thumb: Want to keep the resource alive but stop tracking it? state rm. Want it gone for good? destroy.
Q57.Explain the process of migrating state from a local backend to a remote backend such as S3 or Terraform Cloud.
S3 or Terraform Cloud.You add a backend block for the remote target and run terraform init, which detects the change and offers to copy your existing local state to the new backend.
Back up the current state: Copy the local terraform.tfstate somewhere safe before touching anything.
Declare the backend: Add a backend "s3" (or cloud) block in the terraform block with bucket/key/region or org/workspace.
Run terraform init: Terraform notices the backend changed and prompts: "Do you want to copy existing state to the new backend?"; answer yes.
Verify: Run terraform plan: a clean "no changes" confirms the migration preserved state exactly.
Clean up: Remove/secure the now stale local state so no one uses it by accident.
Q58.What is the difference between terraform state mv and terraform state rm? When would you use each?
terraform state mv and terraform state rm? When would you use each?terraform state mv renames or relocates a resource within state (or into another state) while keeping the real infrastructure, whereas terraform state rm deletes an entry from state entirely so Terraform stops tracking it; neither touches the live resource.
terraform state mv:
Updates the address a resource is tracked under: after a refactor, module rename, or count/for_each change.
Prevents Terraform from destroying and recreating a resource just because its address changed.
terraform state rm: Removes tracking so the resource becomes unmanaged: use when handing it off, deleting it manually, or before re-importing.
When to use each: Keeping and still managing it under a new name? mv. Keeping it alive but no longer managing it? rm.
Q59.Explain the concept of infrastructure drift. How does Terraform detect it, and what are the strategies for reconciling it?
Infrastructure drift is when the real-world infrastructure no longer matches what Terraform state records, usually because someone changed a resource outside Terraform (console click, manual CLI, auto-scaling). Terraform detects it by refreshing state against the provider APIs and comparing to configuration.
How Terraform detects it:
During plan it refreshes each resource's live attributes and diffs them against state and config.
A dedicated terraform plan -refresh-only surfaces drift without proposing config-driven changes.
Reconciliation strategies:
Revert: run terraform apply so Terraform pushes the resource back to the declared config (treats code as source of truth).
Accept: update the configuration to match reality, or apply a -refresh-only plan to record the new values in state.
Adopt: if the change created new resources, import them into state.
Prevention:
Restrict manual access, enforce changes through CI, and detect drift on a schedule.
Use ignore_changes for attributes legitimately managed elsewhere.
Q60.When would you use terraform import, and what are its limitations regarding the configuration file?
terraform import, and what are its limitations regarding the configuration file?You use terraform import to bring an existing resource that was created outside Terraform under Terraform's management by writing it into state, without recreating it. Its key limitation is that it only populates state: it does not generate the configuration for you.
When to use it:
Adopting pre-existing infrastructure (built manually or by another tool) into IaC.
Recovering resources that fell out of state, or migrating between modules.
The configuration limitation:
You must hand-write a matching resource block first; import fills state but won't author HCL.
If your config doesn't match the imported resource, the next plan shows diffs or destructive changes.
Other limits:
The CLI terraform import <address> <id> imports one resource at a time and isn't part of a plan/apply workflow.
Not all provider resources support import.
Q61.What is the difference between terraform refresh and the newer terraform plan -refresh-only, and when would you use the latter?
terraform refresh and the newer terraform plan -refresh-only, and when would you use the latter?Both reconcile state with real infrastructure, but the old terraform refresh silently wrote drift straight into state with no review, whereas terraform plan -refresh-only shows you the detected drift as a reviewable plan you can approve before it updates state. The standalone refresh command is now deprecated in favor of the safer approach.
terraform refresh (deprecated):
Immediately mutated state to match live resources, no diff shown, no approval.
Risky: silent state changes could hide or cause unexpected behavior.
terraform plan -refresh-only:
Produces a plan of state-only updates that you review, then apply with terraform apply -refresh-only.
Never changes infrastructure, only state.
When to use the latter: To audit out-of-band drift, and to deliberately accept it into state without any config-driven apply.
Q62.When should you use a data source to fetch information about an existing resource versus using the terraform_remote_state data source?
terraform_remote_state data source?Use a regular data source to query live infrastructure attributes from a provider's API; use terraform_remote_state to read outputs another Terraform configuration has already written to its state.
Provider data source: query real infrastructure:
Reads current attributes directly from the API (e.g. aws_ami, aws_vpc) at plan time.
Works even for resources Terraform never created (manually made or owned by another team).
Always reflects live state, not what some other configuration recorded.
terraform_remote_state: read another config's outputs:
Only exposes values explicitly published as output blocks in the upstream state.
Requires read access to the remote state backend and couples the two configurations tightly.
Good for passing composed values between layers (e.g. network layer exposes subnet IDs to an app layer).
Rule of thumb:
Need a live fact about infrastructure, use a provider data source.
Need a value computed by another Terraform config you own, use terraform_remote_state (or better, published outputs / a data source to decouple).
Q63.Explain the concept of Terraform workspaces. How do they differ from using a separate directory structure for different environments?
Workspaces let one configuration and backend hold multiple independent state files, so you can stand up parallel instances (like dev and prod) from the same code. They isolate state, but not the code or backend configuration, which is their key limitation.
What a workspace is:
A named, separate state file within the same backend (default exists automatically).
Switch with terraform workspace select; reference via terraform.workspace for naming or conditionals.
Workspaces vs. separate directories:
Workspaces share the same code, backend, and provider versions: only state differs.
Separate directories give each environment its own backend config, variables, and even different resource shapes.
Why directories are often preferred for environments:
Environments frequently need different backends, credentials, and drift boundaries that a single backend can't express.
Workspaces hide the active environment behind CLI state, which is easy to apply to the wrong target.
Good use for workspaces: Short-lived or identical parallel copies (per-developer sandboxes, ephemeral feature stacks) where code is truly the same.
Q64.When would you use the -target flag, and what are the primary risks of using it in a production environment?
-target flag, and what are the primary risks of using it in a production environment?Use -target to limit a plan/apply to specific resources, mainly to recover from a broken state or work around a bug, not as a normal workflow. It's risky because it deliberately ignores dependencies and can leave your state inconsistent with your configuration.
Legitimate uses:
Recovering from a partial failure by re-applying one resource.
Working around a provider/dependency bug when a full apply misbehaves.
Narrowing a huge plan temporarily during debugging.
Primary risks in production:
Dependencies are only partially processed, so state can drift from the real desired configuration.
It masks the full picture: you may apply a change without seeing everything it affects.
Encourages a piecemeal habit instead of fixing the root cause in code.
Guidance: HashiCorp intends it as an exceptional, break-glass tool; follow it with a full terraform plan to confirm state converges.
Q65.When should you use terraform destroy, and how can you target a specific resource for destruction without affecting the whole stack?
terraform destroy, and how can you target a specific resource for destruction without affecting the whole stack?Use terraform destroy to tear down all infrastructure managed by a configuration (or a subset), typically for ephemeral environments, cleanup, or cost control. To remove a single resource without affecting the rest of the stack, use the -target flag.
When to use it:
Ephemeral or throwaway environments (dev, test, CI sandboxes) that should not linger.
Cost control: destroying expensive resources when idle.
Rarely in production: it deletes real infrastructure and state, so treat it with the same caution as an apply.
How it works:
It plans and executes the deletion of every resource in state, in reverse dependency order.
Equivalent to terraform apply -destroy, which shows the destroy plan first.
Targeting a single resource:
Use terraform destroy -target=aws_instance.web to destroy just that address (and anything that depends on it).
You can also destroy a single resource with terraform apply -destroy -target=....
Caveats with -target:
It is an escape hatch, not routine workflow: it skips dependency reconciliation for untargeted resources and can leave state inconsistent.
Prefer removing the resource from config and running a normal apply, or use terraform state rm if you only want to forget it, not delete it.
Q66.What is a variable validation block, and how do you use it to enforce constraints on input variables?
A validation block lives inside a variable declaration and lets you enforce custom constraints on input values before Terraform proceeds. If the condition evaluates to false, Terraform stops and shows your error_message.
Structure:
condition: a boolean expression that must be true for the value to be accepted.
error_message: the message shown when the condition fails.
Key rules:
The condition may reference only the variable itself (var.<name>), not other variables or resources.
You can have multiple validation blocks per variable; all are checked.
It complements type constraints: type ensures the shape, validation enforces business rules (ranges, patterns, allowed values).
Common uses:
Restrict to an allowed set with contains([...], var.x).
Enforce naming or format with can(regex(...)).
Numeric ranges with comparison operators.
Q67.What are the trade-offs of using a 'Cloud-Agnostic' tool like Terraform versus a 'Cloud-Native' tool like AWS CloudFormation or Azure Bicep?
A cloud-agnostic tool like Terraform gives you one language and workflow across many providers, at the cost of some lag and abstraction; a cloud-native tool like CloudFormation or Bicep gives you the tightest integration with a single vendor, at the cost of lock-in.
Cloud-agnostic (Terraform) advantages:
One tool, one skillset across AWS, Azure, GCP, and hundreds of SaaS providers.
Can compose multi-cloud/multi-provider resources in a single config (e.g. AWS + Cloudflare + Datadog).
Large module ecosystem and consistent HCL workflow.
Cloud-agnostic trade-offs:
New provider features can lag behind the native tool's day-one support.
You manage your own state; native tools manage state for you.
Cloud-native (CloudFormation, Bicep) advantages:
First-party: usually supports new services immediately, deep console/IAM integration.
Vendor-managed state and rollback, no extra tooling or backend to secure.
Cloud-native trade-offs:
Lock-in: knowledge and templates don't transfer to other clouds.
Not usable for the non-cloud pieces of your stack.
Q68.From a software engineer's perspective, what are the trade-offs of using HCL (Terraform) versus a real programming language like TypeScript or Python (Pulumi/AWS CDK)?
Q69.What do the try() and can() functions do, and when are they useful for handling dynamic or uncertain values?
try() and can() functions do, and when are they useful for handling dynamic or uncertain values?Q70.What is the optional() modifier in object type definitions, and how does it help with variable defaults?
optional() modifier in object type definitions, and how does it help with variable defaults?Q71.What is the terraform replace-provider command used for, and when would you need it?
terraform replace-provider command used for, and when would you need it?Q72.Explain the concept of 'Module Composition.' How do you pass data from one module to another?
Q73.What is the flat module versus nested module debate, and why is deep nesting of modules generally discouraged in large-scale environments?
Q74.How do you balance the DRY principle with the need for infrastructure code to be explicit and easy to audit?
Q75.How does Terraform handle the dependency graph, and what happens if there is a circular dependency?
Q76.How does Terraform's resource graph allow it to perform parallel operations?
Q77.What does the -parallelism flag control, and why might you lower it during an apply?
-parallelism flag control, and why might you lower it during an apply?Q78.What is the replace_triggered_by argument in the lifecycle block, and what scenario does it address?
replace_triggered_by argument in the lifecycle block, and what scenario does it address?Q79.What happens to the state file and the infrastructure if your network connection drops or the CLI process is killed in the middle of a terraform apply, and how does state locking protect against this?
terraform apply, and how does state locking protect against this?Q80.If you inherited a monolith Terraform state file with thousands of resources in one state, what is your conceptual strategy for breaking it into smaller, isolated states?
Q81.What happens if the state file is accidentally deleted? How would you attempt to recover or rebuild it?
Q82.What is the difference between terraform state pull and terraform state push, and why are they risky?
terraform state pull and terraform state push, and why are they risky?Q83.What is partial backend configuration, and why would you supply backend settings at init time rather than hard-coding them?
Q84.How does the terraform_remote_state data source work, and what are the security considerations of exposing another state's outputs?
terraform_remote_state data source work, and what are the security considerations of exposing another state's outputs?Q85.How do you handle importing existing infrastructure into Terraform, and what are the limitations of terraform import vs the new import blocks?
terraform import vs the new import blocks?Q86.Explain the difference between terraform plan -refresh-only and a standard terraform plan.
terraform plan -refresh-only and a standard terraform plan.Q87.What is the difference between a local execution and a remote execution in Terraform Cloud, and how does this affect secrets management?
Q88.How does a VCS-driven workflow work in Terraform Cloud, and how does it differ from CLI-driven runs?
Q89.Explain the concept of Policy as Code (Sentinel or OPA) within the Terraform workflow. At what stage of the plan/apply cycle are these policies enforced?
Sentinel or OPA) within the Terraform workflow. At what stage of the plan/apply cycle are these policies enforced?Q90.Explain the purpose of the check block. How does it differ from a standard resource validation or a precondition?
check block. How does it differ from a standard resource validation or a precondition?Q91.How do precondition and postcondition blocks work, and how do they differ from variable validation?
precondition and postcondition blocks work, and how do they differ from variable validation?