State • Modules • count and for_each • Lifecycle • CI/CD • 2026

Terraform Interview Questions

30 questions What each one tests, an answer frame, a spoken answer 32 min read

This page is for anyone facing a Terraform round, whether it's part of a DevOps, cloud or platform interview. Most rounds start with the basics of providers, resources and the plan and apply cycle, then spend real time on state: remote backends, locking, drift and moving resources safely. After that come variables, modules, count versus for_each and lifecycle rules, with senior rounds adding pipeline design, secrets and a story about a change that went wrong. Each question shows what the interviewer is really checking, the shape of a strong answer and a short answer you can say out loud.

Search all questions by round, difficulty and level, or save the ones you want to practise.

Basics 4 questions

Easy Technical round Fresher, Mid-level Practice question

1. Why would a team manage its cloud setup with Terraform instead of creating things by hand in the console?

What the interviewer is really testing:
Whether you understand the real problems infrastructure as code solves, like repeatability, review and recovery, rather than just reciting that it is code.
Answer frame:

Repeatable: the same code builds the same setup in dev, test and production.

Reviewable: changes go through pull requests and a plan shows the effect before anything happens.

Recoverable: the code is the record, so you can rebuild or audit what exists.

Sample spoken answer:

"When people click things together in a console, nobody really knows later what exists or why. Environments slowly drift apart, and rebuilding one after a mistake means guessing. With Terraform the setup is written down as code in Git. That gives me three things. First, repeatability: I can build the same network and servers in dev and production from one config with different inputs. Second, review: a change is a pull request, and terraform plan shows exactly what will be created, changed or destroyed before anyone applies it. Third, history and recovery: Git tells me who changed a firewall rule and when, and if a region or account is lost I can recreate it from code. Terraform in particular works across many providers with one workflow, so the same habits apply to cloud resources, DNS and monitoring."

Red flag to avoid:

Only saying it is faster, without mentioning review, repeatability or the plan step.

They may ask next:
  • What parts of an environment would you still not manage with Terraform?
  • How does Terraform differ from a tool that configures software inside a server?
Say it in 60 seconds
Easy Technical round Fresher Practice question

2. What is the difference between a provider, a resource and a data source in Terraform?

What the interviewer is really testing:
Whether you know the three core building blocks and, in particular, that a data source only reads and never creates anything.
Answer frame:

Provider: a plugin that talks to one API, such as a cloud, and knows its resource types.

Resource: something Terraform creates, updates and destroys, and tracks in state.

Data source: a read-only lookup of something that already exists, used as input.

Sample spoken answer:

"A provider is the plugin that knows how to talk to one platform's API. The AWS provider, the Azure provider and the Kubernetes provider are examples, and terraform init downloads them. A resource is an object Terraform owns: I declare a bucket or a virtual machine, Terraform creates it, remembers it in state, and will change or delete it to match my code. A data source is a read-only lookup. It fetches information about something that already exists, like the latest image matching a filter or a network another team manages, so I can use its ID without owning it. The key difference is ownership: if I delete a resource block, Terraform plans to destroy the real object. If I delete a data source block, nothing in the cloud changes."

Code:
data "aws_vpc" "shared" {
  tags = { Name = "shared-network" }
}

resource "aws_subnet" "app" {
  vpc_id     = data.aws_vpc.shared.id
  cidr_block = "10.0.1.0/24"
}
Red flag to avoid:

Thinking a data source creates the thing it looks up, or mixing up providers with modules.

They may ask next:
  • When would you use a data source instead of hard-coding an ID?
  • How do you configure two instances of the same provider, for example for two regions?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

3. How does Terraform decide the order in which it creates resources, and when do you need depends_on?

What the interviewer is really testing:
Whether you understand the dependency graph and implicit references, and only reach for depends_on when a dependency is truly hidden.
Answer frame:

Implicit: referencing another resource's attribute creates an edge in the graph.

Parallel: resources with no path between them are created at the same time.

Explicit: depends_on is for dependencies Terraform can't see from references.

Sample spoken answer:

"Terraform builds a dependency graph from the code. If my subnet uses the VPC's id, that reference tells Terraform the VPC must exist first, so I rarely write the order myself. Anything with no link between them runs in parallel, which is why a large apply is quicker than doing things one by one. On destroy it walks the graph in reverse. depends_on is for hidden dependencies, where one thing needs another but doesn't reference any of its attributes. A common example is an app server that needs an access policy attached before it starts, even though nothing in the server block points at that policy. I use it sparingly, because it makes the graph more conservative, and it can make data sources wait until apply, which means less is known at plan time."

Red flag to avoid:

Adding depends_on everywhere to be safe, or believing Terraform runs resources top to bottom in file order.

They may ask next:
  • What does a cycle error mean, and how would you break one?
  • Can you put depends_on on a whole module, and what does that do?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

4. How do you pin provider versions, and what is the .terraform.lock.hcl file for?

What the interviewer is really testing:
Whether you know how to keep runs reproducible across laptops and CI, and the difference between a version constraint and the locked version.
Answer frame:

Constraint: required_providers says which versions are allowed, for example a pessimistic range.

Lock file: records the exact version picked and its checksums; commit it.

Upgrade: change versions on purpose with init -upgrade and review the plan.

Sample spoken answer:

"In the terraform block I declare required_providers with a source and a version constraint, usually something like the pessimistic operator so I accept patch or minor updates but not a new major version. That constraint says what's allowed. The lock file, .terraform.lock.hcl, records which exact version init actually chose, plus checksums of the package. I commit it to Git, so every teammate and the CI runner install the same provider build, and a surprise release can't change behaviour between the plan someone reviewed and the apply. When I want to move up, I run terraform init -upgrade, look at the provider changelog, and check the plan shows no unexpected changes. One thing to note is that the lock file covers providers, not modules, so module versions are pinned separately in the module block."

Code:
terraform {
  required_version = ">= 1.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
Red flag to avoid:

Not committing the lock file, or leaving providers unpinned so any new major version can be pulled in.

They may ask next:
  • Why might the lock file need checksums for more than one operating system?
  • How would you pin the version of a module from a registry?
Say it in 60 seconds

Workflow & CI/CD 2 questions

Easy Technical round Fresher Practice question

5. Walk me through what init, plan and apply each do, and what happens on destroy.

What the interviewer is really testing:
Whether you know the day-to-day workflow and what each step touches, especially that plan changes nothing and apply acts on the real infrastructure.
Answer frame:

init: sets up the backend and downloads providers and modules.

plan: refreshes state, compares it with the code and shows the proposed changes.

apply and destroy: apply carries the changes out; destroy removes everything the state tracks.

Sample spoken answer:

"terraform init prepares the working folder. It configures the backend where state lives and downloads the providers and modules the code needs, and it writes a lock file for provider versions. terraform plan reads the current state, checks the real infrastructure for changes, and compares that with my code. It prints what it would create, update in place, replace or destroy, and it doesn't change anything. terraform apply works out the same plan, asks me to confirm unless I pass a saved plan, and then makes the API calls and updates the state. terraform destroy is really a plan to delete everything this state manages, so it's dangerous in shared environments. In a team I usually run plan with -out to save it, have it reviewed, and apply that exact file."

Red flag to avoid:

Saying plan makes changes, or not knowing that destroy removes everything in that state.

They may ask next:
  • Why is applying a saved plan file safer than running apply on its own?
  • What do fmt and validate check, and what can't they catch?
Say it in 60 seconds
Hard System design round Mid-level, Senior Practice question

6. Design a CI/CD pipeline for Terraform. What runs on a pull request and what runs on merge?

What the interviewer is really testing:
Whether you can build a safe team workflow where the change applied is exactly the change that was reviewed.
Answer frame:

On pull request: fmt check, validate, lint and policy checks, then plan posted to the PR.

On merge: plan saved to a file, approval gate for production, apply that exact plan.

Safety: one runner per state at a time, locked versions, no applies from laptops.

Sample spoken answer:

"On every pull request the pipeline runs terraform fmt -check and validate, then a linter and any policy checks, like no public buckets. Then it runs plan against each affected environment and posts the summary to the pull request, so reviewers read the plan, not just the code. On merge to main, it runs plan again with -out to save the plan file, and for production there's a manual approval step where someone reads that plan. Then it applies the saved file, so what gets applied is exactly what was approved. If state changed in between, Terraform refuses the stale plan, which is what I want. I'd make sure only one job runs per state at a time, pin Terraform and provider versions, store plan files as protected artifacts because they can hold secrets, and remove apply rights from people's laptops for production."

Red flag to avoid:

A pipeline that runs apply with auto-approve on every push, with no saved plan or review of the plan output.

They may ask next:
  • What would you do if two merged pull requests touch the same state close together?
  • How would you handle a change that needs a manual step between two applies?
Say it in 60 seconds

State 7 questions

Easy Technical round Fresher, Mid-level Practice question

7. What is the Terraform state file and why does Terraform need it at all?

What the interviewer is really testing:
Whether you understand that state maps code to real objects, and why losing it or editing it by hand is so harmful.
Answer frame:

Mapping: links each resource address in code to a real object ID.

Metadata: stores attributes and dependencies used to plan and to destroy in order.

Care: it can hold secrets, so it needs a secure, shared, backed-up home.

Sample spoken answer:

"The state file is Terraform's record of what it manages. For each resource address in my code, like aws_instance.web, it stores the real object's ID and its attributes as they were after the last run. Terraform needs it because the cloud doesn't know which objects belong to which config. Without state, Terraform would have no idea that the server in my code is that particular server, so it would try to create a new one. State also remembers dependencies, which helps it delete things in the right order after the code for them is gone, and it saves time on large setups. Because it can contain sensitive values in plain text, I treat it like a secret: stored in a remote backend with encryption and access control, never committed to Git, and never edited by hand."

Red flag to avoid:

Committing state to Git, or saying Terraform could simply look up everything in the cloud without it.

They may ask next:
  • What happens if two people apply with separate local copies of the state?
  • Which commands let you inspect state safely?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level, Senior Practice question

8. Why do teams move state to a remote backend, and how does state locking protect them?

What the interviewer is really testing:
Whether you have worked with Terraform in a team and understand the race condition that locking prevents.
Answer frame:

Shared: one copy of state everyone and CI use, not files on laptops.

Locking: plan, apply and other state commands hold a lock, so two runs can't overlap.

Safety: versioning, encryption and tight access on the backend.

Sample spoken answer:

"Local state works for one person, but in a team it breaks quickly. If two people each have their own copy, they overwrite each other's changes or create duplicates. A remote backend puts one copy in a shared place, like an object storage bucket or a managed Terraform service, and every run reads from it. Locking is the second half. When someone runs plan or apply, Terraform takes a lock on that state, and anyone else trying to run against it waits or fails with a lock error. Without it, two applies at once could each read the old state and write conflicting results, which can corrupt it. Each backend locks its own way: some use a lease on the blob, some a separate lock table or lock file. I also turn on versioning for the bucket, so I can recover an older state if something goes wrong."

Red flag to avoid:

Keeping state on a laptop or in Git for a team, or not knowing what locking prevents.

They may ask next:
  • Which backend have you used, and how does it do locking?
  • How would you restore state from an earlier version, and what would you check first?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

9. What is drift, how do you find it, and how do you decide whether to fix the code or the infrastructure?

What the interviewer is really testing:
Whether you can handle the real-world case where someone changed things outside Terraform, and make a sensible call on which side is right.
Answer frame:

Meaning: the real infrastructure no longer matches what state and code say.

Find it: a normal plan shows it; plan -refresh-only shows only the outside changes.

Decide: revert with apply, or copy the change into code if it should stay.

Sample spoken answer:

"Drift is when the real infrastructure differs from what Terraform expects, usually because someone changed something in the console or another tool touched it. A normal plan refreshes state first, so drift shows up as changes Terraform wants to make to put things back. If I only want to see what changed outside Terraform, I run plan with -refresh-only, which shows the difference between state and reality without proposing code changes. Then I decide. If the manual change was a mistake, I apply and Terraform reverts it. If it was a real fix, say an emergency firewall rule during an incident, I write it into the code, so the plan comes back clean. If I only want state to record the outside change, apply -refresh-only does that without touching anything. Some teams run a scheduled plan in CI and alert when it isn't empty, so drift is caught in days, not months."

Red flag to avoid:

Blindly applying to overwrite a manual change without asking why it was made.

They may ask next:
  • How would you stop drift happening in the first place?
  • What does ignore_changes do, and when is it the right answer to drift?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

10. You renamed a resource in the code and the plan wants to destroy and recreate it. How do you rename it safely?

What the interviewer is really testing:
Whether you know that the resource address is the identity in state, and how to move it without touching the real object.
Answer frame:

Why: a new address looks like a new resource, and the old one looks deleted.

moved block: declare the old and new address in code; the plan shows a move, not a replace.

Older way: terraform state mv, run by hand, with a plan afterwards to confirm.

Sample spoken answer:

"Terraform tracks resources by their address, so if I rename aws_instance.web to aws_instance.api, it sees one resource gone and a new one to create. For a database that would be a disaster. The clean fix today is a moved block: I write from the old address and to the new one, and the plan shows it as a move with no changes to the real object. It's reviewed in the pull request like any other change and works for everyone's state, including other workspaces. The older approach is terraform state mv, which edits state directly from the command line. It works, but it's a manual step outside review, so I'd take a state backup first. Either way, the proof is a plan that shows zero adds and zero destroys. The same idea applies when moving a resource into a module or switching from count to for_each."

Code:
moved {
  from = aws_instance.web
  to   = aws_instance.api
}

moved {
  from = aws_s3_bucket.logs
  to   = module.logging.aws_s3_bucket.this
}
Red flag to avoid:

Accepting the destroy and recreate on a stateful resource, or hand-editing the state JSON.

They may ask next:
  • When can you delete the moved block again?
  • How would you move a resource from one state file to a completely different one?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

11. Some infrastructure was built by hand. How do you bring it under Terraform without recreating it?

What the interviewer is really testing:
Whether you know how import works end to end, including that importing adds to state but the code still has to match.
Answer frame:

Write or generate code: a resource block that describes the object.

Import: an import block, or the terraform import command, links the address to the real ID.

Reconcile: adjust the code until plan shows no changes.

Sample spoken answer:

"Import tells Terraform that an existing object belongs to a resource address in my code. The newer way is an import block: I write the target address and the real ID, run plan, and Terraform shows it will import rather than create. With -generate-config-out it can even draft the resource block for me, which I then clean up. The older way is the terraform import command, which writes straight to state and needs the resource block to exist already. Either way, importing only fills in state. The real work is making the code match reality, so I keep running plan and adjusting arguments until it shows nothing to change. I'd import in small batches, starting with things that are low risk, and I'd never apply while the plan still shows changes to a production object I don't understand."

Code:
import {
  to = aws_s3_bucket.reports
  id = "company-reports-bucket"
}

resource "aws_s3_bucket" "reports" {
  bucket = "company-reports-bucket"
}
Red flag to avoid:

Thinking import writes the code for you in all cases, or applying straight after import without a clean plan.

They may ask next:
  • What goes wrong if the code doesn't match the imported object and you apply anyway?
  • How would you find the right import ID format for a resource type?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

12. During an incident someone opened a firewall port in the console to restore service. The next plan wants to remove it. How do you handle it?

What the interviewer is really testing:
Whether you respect the emergency fix while getting the code back to being the source of truth, without breaking service again.
Answer frame:

Don't revert: an apply now would undo the fix and restart the incident.

Codify: add the rule to the code through a normal pull request so the plan is clean.

Follow up: ask if the fix was right long term, and agree how emergency changes get recorded.

Sample spoken answer:

"The worst thing I could do is let the next routine apply remove that port and take the service down again. So first I'd tell the team, so nobody applies that config for now. Then I'd talk to whoever made the change and understand why it was needed. If the rule should stay, I add it to the code in a pull request, maybe tighter than the emergency version, and confirm the plan shows no change to the live rule, or only the tightening we agreed. If it was a workaround, we fix the real cause and then remove the rule through Terraform. Afterwards I'd suggest a simple rule for incidents: console changes are allowed, but they get a ticket to codify within a day, and a scheduled drift plan checks that they did."

Red flag to avoid:

Applying immediately to put things back the way the code says, or scolding the engineer who kept the service up.

They may ask next:
  • What would you do if the person who made the change isn't reachable?
  • How would you make scheduled drift checks useful rather than noisy?
Say it in 60 seconds
Hard Situational round Senior Practice question

13. One state file now holds hundreds of resources and every plan takes a long time. The team wants to split it. How would you do it safely?

What the interviewer is really testing:
Whether you can plan a state split with no resource destroyed or recreated, and choose sensible boundaries.
Answer frame:

Boundaries: split by how often things change and who owns them, such as network, data and apps.

Move: import into the new state, remove from the old one without destroying.

Prove: both plans show no changes before and after; one piece at a time.

Sample spoken answer:

"First I'd pick boundaries that match how things change: the network rarely changes, databases change carefully, and application pieces change daily, so those make natural separate states. Different owners are another good boundary. Then I'd move one piece at a time. In the new configuration I add the resource code plus import blocks, and in the old one I use removed blocks with destroy set to false, or terraform state rm, so the old state forgets the objects without deleting them. The rule is that both plans must show zero adds and zero destroys before anything is applied. Values the pieces share, like subnet IDs, become outputs or data source lookups instead of direct references. I'd back up the state first, freeze other changes to that state during the move, and do the low-risk parts first to build confidence."

Red flag to avoid:

Deleting resources from the old config and letting them be recreated in the new one, or doing the whole split in one big change.

They may ask next:
  • How do the new pieces reference each other once they are separate?
  • How would you stop a later change from accidentally recreating a moved resource in the old config?
Say it in 60 seconds

Troubleshooting 4 questions

Easy Technical round Fresher, Mid-level Practice question

14. A run failed and now every plan says the state is locked. What do you do?

What the interviewer is really testing:
Whether you can clear a stale lock safely, confirming first that nobody is really running, instead of forcing it straight away.
Answer frame:

Check: read the lock info: who holds it, when, from which operation.

Confirm: make sure that run or pipeline is truly dead, not just slow.

Release: terraform force-unlock with the lock ID, then plan again.

Sample spoken answer:

"The lock error prints the lock ID, who took it, and when. First I check whether that run is really gone. Often a CI job was cancelled or a laptop lost its connection mid-apply, so the lock was never released. I'd look at the pipeline or message the person named. If someone is actually applying right now, I just wait, because forcing the lock while they write state is how you corrupt it. Once I'm sure nothing is running, I use terraform force-unlock with that lock ID, then run a plan to see what state things are in. If the failed run was an apply, the plan might show some resources half-created, so I read it carefully before applying again. Afterwards I'd look at why the lock leaked, for example pipelines that kill jobs without a grace period."

Red flag to avoid:

Force-unlocking immediately without checking whether another run is in progress, or deleting the lock record by hand.

They may ask next:
  • Why is force-unlock dangerous if someone is still running an apply?
  • How can a CI setup make stale locks less likely?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

15. An apply fails halfway through with an API error. What state are you left in, and how do you recover?

What the interviewer is really testing:
Whether you know Terraform does not roll back, and how to recover calmly from a partial apply.
Answer frame:

No rollback: what succeeded stays created and is saved in state.

Tainted: a resource that was created but failed during setup may be marked for replacement.

Recover: read the error, fix the cause, plan again, apply the rest.

Sample spoken answer:

"Terraform doesn't roll back. Everything that completed before the error is real and is recorded in state, and the rest simply wasn't done. If a resource was created but failed partway through, for example a provisioner errored, Terraform marks it tainted, so the next plan will replace it. So my steps are: read the actual error first, because it's often quotas, permissions, a name already in use or a timeout. Fix that cause. Then run plan, which shows only the remaining work plus any tainted replacements, and check it makes sense before applying. What I don't do is delete things by hand in the console to get a clean start, because that creates drift. If the failure was a timeout, I check whether the object actually finished creating before retrying, so I don't end up with a duplicate outside state."

Red flag to avoid:

Expecting Terraform to roll back automatically, or deleting resources by hand to start over.

They may ask next:
  • What does it mean when a resource is tainted, and can you undo that?
  • How would you design changes so a half-finished apply does less damage?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

16. Apply fails with an error saying the resource already exists. What does that tell you and how do you fix it?

What the interviewer is really testing:
Whether you can reason about the mismatch between state and reality rather than just renaming things until the error goes away.
Answer frame:

Meaning: the object exists in the cloud but not in this state.

Causes: built by hand, lost state, another config owns it, or a failed earlier run.

Fix: import it if this config should own it; otherwise rename or use a data source.

Sample spoken answer:

"It means Terraform is trying to create something with a name or ID that's already taken, and this state has no record of it. So the question is why. Maybe someone created it by hand, or a previous apply timed out after the object was actually created, or state was lost, or a different Terraform config already owns it. I'd look at the object's tags and creation details to find out. If this config should own it and nothing else manages it, I import it, then make the code match until the plan is clean. If another config already manages it, I must not import it here, because two states managing one object will fight each other. In that case I'd read it with a data source or pick a different name. Renaming blindly just leaves an orphaned object behind."

Red flag to avoid:

Importing the object without checking whether another configuration already manages it.

They may ask next:
  • Why is it dangerous for two states to manage the same object?
  • How would you find out which configuration created an existing resource?
Say it in 60 seconds
Hard Situational round Mid-level, Senior Practice question

17. A small pull request's plan shows it will destroy and recreate the production database. The author says it's just a tag change. What do you do?

What the interviewer is really testing:
Whether you stop and find the real cause of a replacement, and protect the data, instead of trusting the description of the change.
Answer frame:

Stop: don't approve; the plan is the truth, not the description.

Find the cause: the plan marks which attribute forces replacement.

Fix safely: revert or change that attribute, move the address, or plan a proper migration.

Sample spoken answer:

"I'd block the merge straight away, because the plan is what will happen, whatever the pull request says. Then I'd read the plan for the line marked forces replacement. It's often not the tag at all. Common causes are an attribute that can't be changed in place, like the instance identifier or the storage encryption setting, a provider upgrade that changed a default, a resource renamed or moved into a module without a moved block, or a count index that shifted. Once I know the attribute, the fix follows: revert that change, add a moved block, or pin the provider back. If the change really is needed and truly forces replacement, that's a planned migration with a snapshot, a maintenance window and a restore test, not a routine merge. Afterwards I'd put prevent_destroy on the database and add a pipeline check that flags any destroy of stateful resources."

Red flag to avoid:

Approving because the description says it's harmless, or adding ignore_changes to hide the diff without understanding it.

They may ask next:
  • How would you make the pipeline fail automatically when a plan destroys a database?
  • If the change truly requires replacement, how would you migrate the data?
Say it in 60 seconds

Variables & Outputs 3 questions

Medium Technical round Fresher, Mid-level Practice question

18. How do input variables get their values, and which one wins if a value is set in more than one place?

What the interviewer is really testing:
Whether you know the ways variables are set and their order, which matters when a pipeline and a tfvars file disagree.
Answer frame:

Declare: type, description, optional default, validation, sensitive.

Sources: default, TF_VAR_ environment variables, tfvars files, command-line flags.

Order: later sources override earlier ones; command-line flags win.

Sample spoken answer:

"I declare a variable with a type, a description, and a default only if a sensible one exists. Then the value can come from several places. From lowest to highest: the default, environment variables named TF_VAR_ plus the name, the terraform.tfvars file, then terraform.tfvars.json, then any .auto.tfvars files in name order, and finally -var and -var-file flags on the command line, where the last one given wins. If nothing sets a required variable, Terraform prompts for it, which is why I always run with -input=false in CI so it fails fast instead of hanging. I also like validation blocks, so a bad value like a wrong environment name fails at plan with a clear message, not halfway through an apply. For per-environment values I keep a separate tfvars file per environment and pass it with -var-file."

Code:
variable "environment" {
  type        = string
  description = "Deployment environment"

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment must be dev, staging or prod."
  }
}
Red flag to avoid:

Not knowing that command-line flags override tfvars, or putting secrets as defaults in the variable block.

They may ask next:
  • What does marking a variable as sensitive actually protect?
  • When would you use an object type instead of several separate variables?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

19. When do you use a local value instead of an input variable?

What the interviewer is really testing:
Whether you keep the module's public inputs small and use locals for values computed inside, instead of exposing everything as a variable.
Answer frame:

Variables: inputs set by the caller; part of the module's interface.

Locals: names for values computed inside, not settable from outside.

Use: locals to avoid repeating expressions, like common tags or name prefixes.

Sample spoken answer:

"An input variable is something the caller of the configuration or module decides, like the environment name or the instance size. It's part of the public interface. A local is a named expression inside the module that nobody outside can override. I use locals to avoid repeating myself and to give a clear name to something computed. A common example is a name prefix built from the project and environment, or a map of standard tags merged with extra tags passed in. If I made those variables instead, callers could set them to inconsistent values, and the interface gets noisy. My rule of thumb is: if a user genuinely needs to choose it, it's a variable. If it's derived from other values, it's a local. Too many locals chained together can hurt readability, so I keep them simple."

Red flag to avoid:

Making every value a variable, or saying locals and variables are interchangeable.

They may ask next:
  • Can a local refer to a resource attribute, and what does that mean for the plan?
  • How would you apply a standard set of tags to every resource?
Say it in 60 seconds
Medium Technical round Mid-level Practice question

20. What are outputs used for, and how can one Terraform configuration use values from another?

What the interviewer is really testing:
Whether you know how outputs connect modules and separate states, and the trade-offs of reading another team's state.
Answer frame:

Outputs: expose values from a module or a root config, like an ID or endpoint.

Between modules: the parent reads module.name.output_name.

Between states: terraform_remote_state, or a data source lookup, or a shared parameter store.

Sample spoken answer:

"Outputs are how a configuration or module exposes values. Inside one config, a module's outputs are how the parent gets things like a subnet ID or a load balancer address, using module dot name dot output. At the root, outputs are printed after apply and can be read with terraform output, which is handy for scripts. Between separate states, say the network team's state and my application state, I have a few options. terraform_remote_state reads the other state's root outputs directly, but it means my config needs read access to their whole state, which can include secrets. Often a cleaner option is a normal data source that looks the network up by tags, or having the network config publish values to a parameter store. Outputs can be marked sensitive, which hides them in the CLI but not in state."

Red flag to avoid:

Thinking a sensitive output is encrypted or removed from state.

They may ask next:
  • Why can reading another team's state be a security concern?
  • What happens to a module's internal values if you don't output them?
Say it in 60 seconds

Meta-Arguments 3 questions

Medium Coding round Fresher, Mid-level, Senior Practice question

21. When would you choose for_each over count, and what goes wrong if you use count for a list of named things?

What the interviewer is really testing:
Whether you know the index-shift problem with count, which destroys and recreates resources when a list changes.
Answer frame:

count: makes N copies addressed by number; good for identical copies or on/off switches.

The trap: remove an item from the middle and every later index shifts, so resources get replaced.

for_each: keys each instance by a stable string, so only the removed one is destroyed.

Sample spoken answer:

"count creates copies addressed by position, like aws_iam_user.dev[0] and [1]. That's fine for identical things, or the common trick of count equals one or zero to switch a resource on or off. The problem comes when I use count over a list of named things. If I remove the first name, every later name moves up one index, and Terraform sees each index now has a different name. So it updates or even replaces the resources after it, which for users or buckets is a real outage. for_each takes a map or a set of strings, and each instance is keyed by that string, like aws_iam_user.dev with the key alice. Removing one entry only destroys that one. One limit: the keys must be known at plan time, so I can't key on an ID that only exists after apply."

Code:
variable "developers" {
  type = set(string)
}

variable "enable_debug" {
  type = bool
}

# keyed by name: removing one user touches only that user
resource "aws_iam_user" "dev" {
  for_each = var.developers
  name     = each.key
}

# count as an on/off switch
resource "aws_cloudwatch_log_group" "debug" {
  count = var.enable_debug ? 1 : 0
  name  = "/app/debug"
}
Red flag to avoid:

Using count over a list of names and not seeing the index shift problem.

They may ask next:
  • How would you migrate existing count resources to for_each without recreating them?
  • Why can't for_each keys depend on values only known after apply?
Say it in 60 seconds
Medium Coding round Mid-level Practice question

22. How do you generate a variable number of nested blocks, like ingress rules, from a list of inputs?

What the interviewer is really testing:
Whether you know dynamic blocks and when they make code clearer versus harder to read.
Answer frame:

dynamic: loops over a collection to produce repeated nested blocks.

content: the body of each block; the iterator is named after the block.

Restraint: use it for real variable input, not to hide simple static config.

Sample spoken answer:

"A whole resource can be repeated with for_each, but nested blocks inside a resource, like ingress rules in a security group, need a dynamic block. I write dynamic followed by the block name, give it a for_each over a list or map, and put the block's body inside content. Inside content, the iterator takes the block's name by default, so I read ingress.value.port for each rule, or I can rename the iterator if it's clearer. This keeps a module flexible: callers pass a list of rules and the module builds them. That said, I don't use dynamic blocks just to be clever. If the config is fixed, writing the blocks out plainly is easier to review. And for security group rules specifically, some providers now offer separate rule resources, which can be easier to manage one by one."

Code:
variable "ingress_rules" {
  type = list(object({
    port  = number
    cidrs = list(string)
  }))
}

resource "aws_security_group" "app" {
  name = "app"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = "tcp"
      cidr_blocks = ingress.value.cidrs
    }
  }
}
Red flag to avoid:

Wrapping everything in dynamic blocks so the code can't be read without running a plan.

They may ask next:
  • How would you make an optional nested block appear only when a variable is set?
  • What is the difference between a dynamic block and a for expression?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

23. Explain create_before_destroy, prevent_destroy and ignore_changes, with a case where you'd use each.

What the interviewer is really testing:
Whether you have used lifecycle rules for real problems and know their limits, such as what prevent_destroy does not protect against.
Answer frame:

create_before_destroy: build the replacement first to avoid downtime; watch unique names.

prevent_destroy: any plan that would destroy the resource errors out.

ignore_changes: stop Terraform fighting attributes changed by something else.

Sample spoken answer:

"These three change how Terraform handles a resource's life. create_before_destroy flips the default order on replacement: the new object is built first, then the old one is removed. I use it for things like a TLS certificate attached to a load balancer, where a gap would cause downtime. The catch is names: if the name must be unique, both can't exist at once, so I use a name prefix. prevent_destroy makes any plan that would delete the resource fail, which I put on production databases and state buckets. But it only works while the block is in the code; if someone deletes the whole resource block, the protection goes with it. ignore_changes tells Terraform not to revert certain attributes. A typical case is an autoscaling group's desired count, which the autoscaler changes, or tags added by another system."

Red flag to avoid:

Saying prevent_destroy stops deletion in every case, or using ignore_changes on everything to silence a noisy plan.

They may ask next:
  • What does replace_triggered_by do, and when have you needed it?
  • How would you force one resource to be recreated without changing the code?
Say it in 60 seconds

Modules & Environments 3 questions

Easy Technical round Fresher, Mid-level Practice question

24. What is a Terraform module, and when is it worth writing one?

What the interviewer is really testing:
Whether you see modules as a way to package a repeated pattern behind a small interface, not as a folder for every resource.
Answer frame:

Definition: any folder of .tf files; the root module calls child modules.

Interface: variables in, outputs out, the details hidden inside.

When: a pattern repeated across environments or teams, with a clear purpose.

Sample spoken answer:

"Every folder of Terraform files is a module, and the one I run commands in is the root module. When I call another folder or a registry module with a module block, that's a child module. I pass it inputs as arguments, and it gives back outputs. The point is packaging. If every service needs the same pattern, say a bucket with encryption, versioning and a lifecycle policy, I write it once as a module and every team gets the safe defaults. It's worth writing one when the pattern repeats and has a clear job. It's not worth wrapping a single resource just to rename its arguments, because that adds a layer to maintain and debug with no real gain. For shared modules I pin a version, so an update to the module doesn't change everyone's infrastructure on their next plan."

Red flag to avoid:

Creating a module for every single resource, or calling shared modules without pinning a version.

They may ask next:
  • How do you pass a provider configuration into a child module?
  • What would make you choose a public registry module over writing your own?
Say it in 60 seconds
Hard System design round Senior Practice question

25. You are building a module that many teams will use to create their databases. How do you design it?

What the interviewer is really testing:
Whether you can design a reusable interface with safe defaults, versioning and a way to evolve it without breaking callers.
Answer frame:

Interface: few required inputs, typed and validated; safe defaults for backups and encryption.

Guard rails: hard-code what must never vary, expose what teams really need to choose.

Lifecycle: semantic versions, a changelog, moved blocks, tests and examples.

Sample spoken answer:

"I'd start by asking what teams actually need to choose: engine version, size, maybe the network. Those become typed variables with validation. Things that must never vary, like encryption on, public access off, backups kept, go in the module without a switch, because each switch is a way to get it wrong. I'd keep outputs to what callers need, like the endpoint and a secret reference, and turn on the database's own deletion protection by default. I'd avoid prevent_destroy inside the module, because lifecycle settings can't come from a variable, so no caller could ever switch it off. For evolution, the module lives in its own repo or registry with semantic versions and a changelog. Callers pin a version and upgrade on purpose. If I rename things inside, I ship moved blocks so nobody's database gets replaced. Before release, I'd run terraform test against an example config in a test account, and ship a README with a working example."

Red flag to avoid:

Exposing every argument of the underlying resource as a variable, which makes the module a thin wrapper with no guard rails.

They may ask next:
  • How would you roll out a breaking change to a module that fifty configs use?
  • How would you test a module automatically before releasing it?
Say it in 60 seconds
Hard System design round Mid-level, Senior Practice question

26. Would you use Terraform workspaces or separate state per folder to manage dev, staging and production?

What the interviewer is really testing:
Whether you understand what workspaces really isolate and the risks of sharing one config and backend across environments.
Answer frame:

Workspaces: same code and backend, one state per workspace; easy to apply to the wrong one.

Separate roots: a folder or config per environment calling shared modules; separate backends and credentials.

Choice: workspaces for short-lived copies; separate roots for real environment isolation.

Sample spoken answer:

"CLI workspaces give one configuration several states in the same backend, and you switch with terraform workspace select. They're handy for identical, short-lived copies, like a test stack per feature branch. For dev, staging and production I prefer separate root configurations, one folder per environment, each calling the same versioned modules with its own tfvars and its own backend. The reasons are practical. With workspaces, the only thing between me and production is which workspace is selected, and it's easy to apply to the wrong one. They also share the same backend and usually the same credentials, so I can't give dev access without production access. Environments also tend to differ over time, and separate folders make that visible instead of burying it in conditionals on terraform.workspace. The cost is a bit of repetition, which modules keep small."

Red flag to avoid:

Saying workspaces give full isolation between environments, or not knowing they share one backend.

They may ask next:
  • How would you stop someone applying production changes from a laptop?
  • How do you promote a change from staging to production in a folder-per-environment setup?
Say it in 60 seconds

Security 1 questions

Medium Technical round Mid-level, Senior Practice question

27. How do you handle passwords and API keys in Terraform so they don't leak?

What the interviewer is really testing:
Whether you know that secrets end up in state and plan files, and that sensitive only hides values from the screen.
Answer frame:

Never in code: no secrets in .tf or committed tfvars files.

Fetch or generate: read from a secrets manager, or let the cloud generate and store it.

Protect state: encrypted backend, tight access, and remember sensitive only redacts output.

Sample spoken answer:

"First, secrets never go in the code or in committed tfvars files. I either pass them in from the pipeline's secret store as environment variables, read them with a data source from a secrets manager, or, better, let the platform create and store them, like a database that manages its own master password in a secret service, so Terraform only handles a reference. The part people miss is state. Any secret Terraform reads or sets is usually written into the state file in plain text, and it can appear in saved plan files too. Marking a variable or output as sensitive only hides it from the terminal and logs; it's still in state. So the backend must be encrypted, access limited to the people and pipelines that need it, and plan files treated as secrets. Newer Terraform versions add ephemeral values and write-only arguments, which keep a secret out of state entirely where the provider supports them."

Red flag to avoid:

Believing sensitive = true encrypts the value or keeps it out of state.

They may ask next:
  • Who in your team would have read access to the state bucket, and why?
  • How would you rotate a database password that Terraform created?
Say it in 60 seconds

Real Work 3 questions

Hard Behavioral round Mid-level, Senior Practice question

28. Tell me about a Terraform change that caused a problem in a real environment. What happened and what did you change afterwards?

What the interviewer is really testing:
Whether you own a real mistake, understand its root cause in how Terraform works, and improved the process, not just the one config.
Answer frame:

What happened: the change, what broke, and who noticed.

Root cause: why the plan or review didn't catch it.

Fix and lesson: the recovery and the lasting guard rail you added.

Sample spoken answer:

"At my last company I changed our storage module to use for_each instead of count, because the index shifting kept bothering me. Staging looked fine, but the production plan showed a few buckets being replaced, and in a long plan that got missed in review. The apply replaced a log bucket, and we lost some recent logs that hadn't been copied elsewhere. Nothing customer facing broke, but it was a real loss. The root cause was that I hadn't written moved blocks for every key, and our review didn't look at the plan summary, only the code. I restored what we could from a replica, then added moved blocks for the rest. After that we posted plan summaries into every pull request, added a check that fails on any destroy of storage or databases unless someone approves it, and put prevent_destroy on buckets holding data."

Red flag to avoid:

A story where the cause was bad luck or someone else, with no change to the process afterwards.

They may ask next:
  • How do you review a plan with hundreds of lines efficiently?
  • What would you do differently if the same refactor came up again?
Say it in 60 seconds
Medium Behavioral round Mid-level, Senior Practice question

29. Describe a time you moved hand-built infrastructure into Terraform. How did you plan it and what did you learn?

What the interviewer is really testing:
Whether you have done a real import project, handled risk sensibly, and can talk about the messy parts, not just the happy path.
Answer frame:

Starting point: what existed, why it needed to change.

Approach: order of work, import method, how you proved nothing changed.

Lesson: what surprised you and what you'd do again.

Sample spoken answer:

"In my last role our main application's networking and load balancers had all been set up by hand over a few years. Nobody fully knew the rules, and we couldn't build a second region with any confidence. I started with an inventory using the cloud's own listing tools, then grouped things by risk. I brought the low-risk parts in first, like log groups and DNS records, using import blocks and generated code that I cleaned up by hand. For every batch, the rule was a plan with zero changes before we merged. The surprise was how many settings differed from what we assumed, like old default values and rules nobody could explain. We documented each one before deciding to keep or remove it. The payoff came a few months later, when we built the second region from the same modules in days instead of weeks."

Red flag to avoid:

Describing a big-bang import with no plan checks, or not being able to name anything that went wrong.

They may ask next:
  • How did you handle settings nobody could explain?
  • How did you stop people from making new manual changes during the project?
Say it in 60 seconds
Medium Behavioral round Mid-level, Senior Practice question

30. Tell me about a time you improved how your team works with Terraform, such as reviews, structure or speed.

What the interviewer is really testing:
Whether you look beyond your own tickets and can make a team's infrastructure work safer or faster, and bring people along.
Answer frame:

Problem: what hurt: slow plans, risky applies, messy repo, unclear ownership.

Change: what you proposed and how you got the team to agree.

Result: what got better, in plain terms.

Sample spoken answer:

"At my last company everyone ran applies from their own laptop with slightly different Terraform versions, and twice we had a state lock left behind and a confusing mess afterwards. I wrote a short proposal: pin the Terraform version, run all plans and applies in CI, post the plan to every pull request, and require approval for production. Some people worried it would slow them down, so I started with one low-risk environment and asked two sceptical teammates to try it for a couple of weeks. Plans in pull requests turned out to be the part everyone liked, because reviewers could see the real effect instead of guessing from code. After that we rolled it out to production. We stopped seeing version mismatches entirely, and changes to production became routine instead of something people avoided on Fridays."

Red flag to avoid:

A story about tooling alone, with no mention of how the team was brought along or what actually improved.

They may ask next:
  • What pushback did you get, and how did you handle it?
  • What would you improve next in that setup?
Say it in 60 seconds
Were you asked something else? Share it A person checks every question before it goes on the site. No name is shown.
For the call itself

The questions above are the prep. The call has ten more.

ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your resume and notes are never stored on our servers. It stays out of screen share on every plan; only you can see it.

Download ClapAssist with 10 free minutes
Mac and Windows · Stays out of screen share · No card