Terraform – Interview Questions

August 24, 20268 min readUpdated 8/24/2026

The questions Terraform interviews actually ask, with answers the length you would say out loud. The good ones are all really asking the same thing: do you understand state?

What is Terraform, in one sentence?

A tool that takes a description of infrastructure, works out the difference between that description and reality, shows you the difference, and then makes it go away.

The part worth adding: it is declarative. You do not write "create a VPC" — you write "this VPC exists", and running it twice does nothing the second time.

What is the state file for, and why can't Terraform work without it?

The question everything else hangs off.

State maps your resource addresses to real object ids. AWS can tell Terraform whether a bucket exists and how it is configured, but it cannot answer "did I create this?" — and without that, deleting a resource from your files would mean nothing, because Terraform would have no record that it was ever responsible for it.

It also caches every attribute, which is how plan can show a diff, and how Terraform can tell "someone changed this in the console" from "this is how it has always been".

Follow-up you should volunteer: state contains secrets in cleartext. Passwords, generated keys, anything a resource holds. That is why it never goes in git and why it lives in an encrypted bucket.

count versus for_each?

The one that separates people who have used Terraform from people who have read about it.

count keys resources by list position. for_each keys them by map key or set value.

It matters when something is removed from the middle. With count, everything after the removed element shifts down an index, so Terraform sees each one change identity — and plans to modify and destroy resources you never touched. With for_each, only the removed key is affected.

So: for_each by default. count for a number you control, or for the count = var.enabled ? 1 : 0 conditional idiom.

How does locking work?

Two applies at once would both read state, both write, and the second would overwrite the first — so Terraform now believes it did not create things that exist.

With the S3 backend, Terraform takes a lock before any operation that writes. Modern versions use use_lockfile = true, which writes a .tflock object next to the state and relies on S3's conditional writes to refuse a second one.

Say this next, because it dates you: historically this needed a DynamoDB table, because S3 had no conditional writes. Terraform 1.10 added native S3 locking and 1.11 deprecated dynamodb_table. Almost every tutorial still shows the table.

Terraform versus CloudFormation?

CloudFormation is AWS-only, free, AWS-supported first for new services, and its state is managed by AWS rather than by you. Terraform is multi-provider — the same language for AWS, Cloudflare, Datadog, GitHub, Kubernetes — with a much better plan output and a state file you own.

The honest trade: owning the state is Terraform's biggest advantage and its biggest operational burden. CloudFormation never loses your state file because you never had one.

Terraform versus Ansible?

Different jobs. Terraform provisions — it creates the server, the database, the load balancer. Ansible configures — it installs packages and edits files on a machine that already exists. They are frequently used together.

Ansible can create cloud resources, but it has no plan step and no state, so it cannot tell you what it is about to change or notice that something drifted.

What happens if someone changes something in the console?

Nothing breaks. The next plan refreshes from AWS, sees the difference, and offers to put it back — your files are the source of truth.

To look without proposing changes: terraform plan -refresh-only. A scheduled job running that and alerting on a non-empty result is a cheap drift detector.

How do you rename a resource without destroying it?

A moved block:

moved {
  from = aws_s3_bucket.old
  to   = aws_s3_bucket.new
}

It is code, so it is checked in and works for everyone including CI. It replaced terraform state mv, which did the same thing as a manual command each person had to run.

Without it, Terraform sees one address disappear and another appear, and plans destroy-then-create.

How do you bring existing infrastructure under Terraform?

An import block plus the matching resource block, then plan to see what it would adopt and what it thinks should change. plan -generate-config-out=x.tf will write the resource block for you.

The older terraform import command still exists, but the block form is reviewable and shows you the diff before you commit to it.

How would you recover a corrupted or deleted state file?

They want to hear that you took precautions first.

Prevention: S3 versioning on the state bucket. Recovery is restoring the previous object version, which is why versioning is not optional.

If it is genuinely gone: the infrastructure still exists and nothing knows it is yours. You import every resource back, one at a time — tedious but tractable if the stack is small, which is an argument for splitting state by blast radius.

What you do not do is run apply and let it recreate everything. Terraform would try to create resources whose names are already taken and fail halfway.

What is a provider?

The plugin that translates HCL into API calls. Terraform core is a language and a graph engine and knows nothing about AWS.

Providers are versioned separately and ship breaking changes in major versions, which is why you pin ~> 6.0 and commit .terraform.lock.hcl.

Why commit the lock file?

The constraint says what is allowed; the lock file records what was chosen. Without it, your laptop and CI can resolve different provider versions and produce different plans from the same code. It also holds checksums, so a tampered registry response fails init.

Bonus point: hashes are per-platform. Generate the lock on a Mac, commit it, and Linux CI fails — run terraform providers lock -platform=linux_amd64 too.

How do you handle multiple environments?

Three ways, and having an opinion matters more than which one.

Workspaces — one directory, multiple states. Good for ephemeral copies; bad for production, because which environment you are in is a property of your shell rather than of anything reviewable, and everything shares one backend.

A directory per environment calling shared modules — explicit, separately backed, and a diff to environments/prod/ is visibly a production change. This is the usual answer.

One directory with per-environment tfvars and partial backend config — no duplication, but the environment is on your command line, so it needs a wrapper.

What does terraform taint do?

Slightly a trick question. It marked a resource for recreation, and it is deprecated. The modern form is:

terraform apply -replace=aws_ecs_service.this

Better, because it goes through plan — you see the destroy before agreeing to it, rather than marking state and finding out later.

When would you use depends_on?

Only when a dependency is real but invisible — nothing in the arguments references the other resource. Referencing an attribute already creates the dependency, and that covers almost everything.

A concrete example: ECS validates it can assume the task execution role when the service is created, and IAM is eventually consistent. The service does not reference the role's policy attachment in any argument, so without depends_on the apply races IAM and fails on a role that "does not exist" — intermittently, which is worse.

Over-using it serialises the graph and slows every apply.

What is a module, and when would you not write one?

A directory of .tf files, called from another. The directory you run apply in is already one.

The second half is the interesting half. Do not write one for a single resource — it adds a layer and buys nothing. Do not write one until there is a second caller, or you will design the interface from one example and get it wrong. Do not write one to organise a single stack; multiple files in one directory do that job.

How do you keep secrets out of Terraform?

Best answer, and it is a specific technique rather than a principle: have the platform generate and hold the secret, and only ever reference its ARN.

resource "aws_db_instance" "this" {
  identifier     = "pizza-prod-mysql"
  engine         = "mysql"
  instance_class = "db.t4g.micro"
  username       = "pizza"

  # RDS generates the password into Secrets Manager. Terraform never sees it.
  manage_master_user_password = true
}

Then hand the secret's ARN to whatever needs the value — an ECS task definition resolves it at container start, so the value never enters the task definition, the state or the console.

And say what does not work: sensitive = true redacts console output and changes nothing about what is written to state.

What is in a plan's output?

+    create
~    update in place
-    destroy
-/+  destroy then create   <-- replacement
+/-  create then destroy   <-- replacement, create_before_destroy

The one to talk about is -/+. Some arguments cannot be changed on a live resource, so changing them means delete and recreate. Terraform says forces replacement next to the attribute responsible. On a database that is data loss from a one-line diff, and reading for it is the core discipline of using Terraform safely.

Is Terraform still open source?

A current-events question, and worth knowing.

No — HashiCorp moved it from MPL 2.0 to the Business Source License in August 2023. The BSL restricts using Terraform to build a competing commercial product; for companies managing their own infrastructure nothing changed in practice.

The last MPL commit was forked as OpenTofu, now a Linux Foundation project. Same HCL, same providers, tofu instead of terraform.

The three that actually get asked most

If you prepare only three:

What is state for? The mapping from your addresses to real ids — and the answer to "did I create this?", which the cloud API cannot give you. Mention the secrets.

count vs for_each? Position versus key, and what happens when you remove the middle element.

How do you keep secrets out? Let the platform own the value, pass the ARN. And sensitive does not encrypt anything.

The end of the track

Sixteen lessons: the plan/apply loop, HCL, providers and state, variables, data sources, remote state, loops, modules, environments, and a real Spring Boot application on ECS Fargate behind a load balancer with a pipeline in front of it.

The stack in lessons 12 and 13 was applied to a real account, verified through the load balancer, and destroyed. What broke is written down where it broke — the health check that cycled tasks forever is the one most worth having read before you meet it.

Start again at lesson 1 if you want the index.