Top 60 Terraform Interview Questions (2026)

The 60 Terraform questions interviewers actually ask, with direct answers, runnable HCL, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.

60 questions with answers

What Is Terraform?

Key Takeaways

  • Terraform is an open-source infrastructure as code tool from HashiCorp that provisions cloud and on-prem resources from declarative HCL configuration.
  • It's declarative: you describe the desired end state, and Terraform builds a dependency graph and figures out the create, update, and destroy steps.
  • State is central. Terraform records what it manages in a state file, and most interview depth lives in state, drift, and the plan-then-apply workflow.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery gets evaluated too.

Terraform is an infrastructure as code tool from HashiCorp that lets you define, provision, and manage infrastructure using a declarative configuration language called HCL (HashiCorp Configuration Language). Instead of clicking through a cloud console, you write files describing the resources you want, virtual machines, networks, databases, DNS records, and Terraform makes the real infrastructure match that description. It's cloud-agnostic through a plugin system of providers, so the same tool manages AWS, Azure, Google Cloud, Kubernetes, and hundreds of other platforms. The core loop is write, plan, apply: you author configuration, run terraform plan to preview changes, then terraform apply to make them real, with Terraform tracking everything it manages in a state file. In interviews, Terraform questions probe how well you understand that state model, the resource dependency graph, and the workflow safeguards, not memorized command flags. This page collects the 60 questions that come up most, each with a direct answer and runnable HCL. If you're still building fundamentals, the official Terraform introduction from HashiCorp is the canonical starting point; increasingly the first DevOps round is a live technical interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
20+Runnable Terraform snippets you can practice from
45-60 minTypical length of a Terraform technical round

Watch: Terraform Course: Automate your AWS cloud infrastructure

Video: Terraform Course: Automate your AWS cloud infrastructure (freeCodeCamp.org, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your Terraform certificate.

Jump to quiz

All Questions on This Page

60 questions
Terraform Interview Questions for Freshers
  1. 1. What is Terraform and what problem does it solve?
  2. 2. What is infrastructure as code, and why does it matter?
  3. 3. Is Terraform declarative or imperative, and why does that matter?
  4. 4. What is the Terraform state file and why is it needed?
  5. 5. What is the difference between terraform plan and terraform apply?
  6. 6. What does terraform init do?
  7. 7. What is a provider in Terraform?
  8. 8. What is a resource block and what are its parts?
  9. 9. What are input variables and how do you use them?
  10. 10. What are output values used for?
  11. 11. What is a .tfvars file and when do you use one?
  12. 12. What does terraform destroy do?
  13. 13. What is HCL?
  14. 14. What do terraform fmt and terraform validate do?
  15. 15. How do you pin Terraform and provider versions, and why does it matter?
  16. 16. What is a module in Terraform?
  17. 17. What is the difference between count and for_each?
  18. 18. What are Terraform workspaces?
  19. 19. What is a data source and how does it differ from a resource?
  20. 20. What does terraform refresh do, and how has it changed?
  21. 21. How do you inspect what Terraform is currently managing?
  22. 22. How does Terraform authenticate to a cloud provider?
  23. 23. How does Terraform decide the order to create resources?
  24. 24. Which Terraform files should you keep out of version control?
Terraform Intermediate Interview Questions
  1. 25. What is a remote backend and why do teams need one?
  2. 26. How does state locking work and why does it matter?
  3. 27. How do you structure a reusable Terraform module?
  4. 28. How do you version and source modules safely?
  5. 29. What kinds of built-in functions does Terraform provide?
  6. 30. How do you express conditional logic in Terraform?
  7. 31. What is a dynamic block and when do you use one?
  8. 32. What are locals and how do they differ from variables?
  9. 33. How do you force Terraform to recreate a single resource?
  10. 34. What are provisioners and why are they a last resort?
  11. 35. How do you bring existing infrastructure under Terraform?
  12. 36. How does Terraform handle sensitive values?
  13. 37. What do terraform state mv and terraform state rm do?
  14. 38. What is a moved block and why is it better than state mv?
  15. 39. How can you see the dependency graph Terraform builds?
  16. 40. What are the main meta-arguments and what does each control?
  17. 41. What does the lifecycle block do?
  18. 42. How do you use the same provider in two regions?
  19. 43. What is terraform console useful for?
  20. 44. What is the .terraform.lock.hcl file for?
  21. 45. How do you manage secrets in Terraform properly?
Terraform Interview Questions for Experienced Engineers
  1. 46. How do you decide how to split Terraform state across a large system?
  2. 47. How do you share values between separate state files?
  3. 48. How do you detect and handle configuration drift in production?
  4. 49. How do you run Terraform safely in CI/CD?
  5. 50. How do you make infrastructure changes without downtime?
  6. 51. A state file is corrupted or lost. How do you recover?
  7. 52. What problem does Terragrunt solve, and is it always needed?
  8. 53. How do you test Terraform code?
  9. 54. How do you enforce guardrails on what Terraform can provision?
  10. 55. Plans on a large configuration are slow. How do you speed them up?
  11. 56. You have resources built with count and need to remove one from the middle. What breaks and how do you handle it?
  12. 57. How do you handle a major provider version upgrade across many states?
  13. 58. How do you organize Terraform code across environments and teams?
  14. 59. How do you migrate state from local to a remote backend, or between backends?
  15. 60. Design question: how would you structure Terraform for a multi-account, multi-environment organization?

Terraform Interview Questions for Freshers

Freshers24 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is Terraform and what problem does it solve?

Terraform is an open-source infrastructure as code tool from HashiCorp that provisions and manages infrastructure from declarative configuration files. You describe the resources you want in HCL, and Terraform creates, updates, or destroys real cloud or on-prem infrastructure to match that description, using providers to talk to each platform's API.

It solves manual, click-through infrastructure: no more untracked changes in a console, no 'how was this set up' mysteries. The config is version-controlled, reviewable, and repeatable across environments, and one tool manages many clouds through providers.

Key point: A one-line definition plus the 'infrastructure in version control' framing beats a feature list. Interviewers open with this to hear how you organize an answer.

Watch a deeper explanation

Video: Terraform in 100 Seconds (Fireship, YouTube)

Q2. What is infrastructure as code, and why does it matter?

Infrastructure as code means defining servers, networks, load balancers, and services in machine-readable files instead of clicking through a console by hand. Those files go in version control, get code-reviewed like any other change, and produce the same infrastructure every time you apply them, on any account or region.

It matters because it makes infrastructure repeatable, auditable, and recoverable. You can spin up an identical staging environment, review a change before it lands, and rebuild from scratch if something is lost.

Key point: Interviewers use this to check whether you grasp the 'why' behind Terraform, not just the commands. Lead with repeatability and review.

Q3. Is Terraform declarative or imperative, and why does that matter?

Terraform is declarative: you describe the desired end state, and Terraform figures out the steps to reach it. You don't write 'create this, then that' as a script would; you write 'these resources should exist', and Terraform reads the references between them, computes the order, and creates independent resources in parallel.

This matters because you get idempotence for free: running apply on an unchanged config does nothing, and Terraform builds a dependency graph to sequence and parallelize the work correctly.

Declarative (Terraform)Imperative (a script)
You writeDesired end stateStep-by-step commands
OrderingTerraform computes itYou write it by hand
Re-runningIdempotent, no-op if unchangedMay duplicate or fail
Drift handlingPlan shows the diffYou detect it yourself

Key point: The word 'idempotent' plus the dependency-graph point is what separates a memorized answer from understanding here.

Q4. What is the Terraform state file and why is it needed?

State is a file (terraform.tfstate) that maps your configuration to the real resources Terraform manages, storing their IDs and attributes. It's how Terraform knows what it already created, so it can plan accurate diffs instead of recreating everything, and it caches attributes so plans stay fast even against a large deployment.

Without state, Terraform couldn't tell an existing resource from a new one. State also caches attributes so plans are fast and lets Terraform detect drift by comparing recorded state against reality.

Key point: State is the single most probed Terraform concept. If you can explain what it maps and why diffs need it, you clear most freshers rounds.

Watch a deeper explanation

Video: Terraform explained in 15 mins (TechWorld with Nana, YouTube)

Q5. What is the difference between terraform plan and terraform apply?

plan is a dry run: it compares your configuration against state and real infrastructure, then prints exactly what it would create, change, or destroy, without touching anything. apply executes those same actions, but only after showing you the plan again and waiting for a yes, so nothing changes by surprise.

The habit the key point is: always read the plan before applying, especially the destroy lines. You can also save a plan to a file and apply that exact plan for safety in CI.

bash
terraform plan -out=tfplan     # preview and save the plan
terraform apply tfplan          # apply exactly what was previewed

Key point: The follow-up is 'what do you look for in a plan?'. Answer: the destroy and replace lines, and the resource count summary.

Q6. What does terraform init do?

init prepares a working directory: it downloads the provider plugins your config needs, installs any modules, and configures the backend where state lives. You run it first in a new directory and again after adding a provider, module, or backend.

It creates the .terraform directory and a dependency lock file that pins provider versions so everyone on the team resolves the same ones.

bash
terraform init          # download providers, set up backend
terraform init -upgrade # re-resolve providers to newer allowed versions

Q7. What is a provider in Terraform?

A provider is a plugin that teaches Terraform how to manage a specific platform: aws, azurerm, google, kubernetes, cloudflare, and hundreds more from the registry. It translates the resources and data sources you declare into that platform's API calls, so Terraform itself stays platform-agnostic and only the provider knows the API details.

You declare providers in a required_providers block with version constraints, and configure each one (region, credentials) so Terraform can authenticate. One config can use several providers at once.

hcl
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

Q8. What is a resource block and what are its parts?

A resource block declares one piece of infrastructure. It has a type that names the provider resource (aws_instance), a local name you choose (web), and a body of arguments configuring it. The type and name together form a unique address, aws_instance.web, that you reference everywhere else in the configuration.

Terraform reads all resource blocks, builds a dependency graph from the references between them, and creates them in the right order.

hcl
resource "aws_instance" "web" {
  ami           = "ami-0abcd1234"
  instance_type = "t3.micro"
  tags = {
    Name = "web-server"
  }
}

Key point: Being able to The three parts (type, name, arguments) and The address aws_instance matters.web signals you've actually written HCL.

Q9. What are input variables and how do you use them?

Input variables parameterize a configuration so the same code works across environments without edits. You declare them with a variable block that sets a type, an optional default, and a description, reference them as var.name inside resources, and set their values from tfvars files, CLI flags, or environment variables at apply time.

They keep hardcoded values out of resource blocks. A variable with no default becomes required, which is a simple way to force callers to supply, say, a region or instance size.

hcl
variable "instance_type" {
  type        = string
  default     = "t3.micro"
  description = "EC2 size for the web tier"
}

resource "aws_instance" "web" {
  instance_type = var.instance_type
  ami           = var.ami_id
}

Q10. What are output values used for?

Outputs expose values from your configuration after apply: a load balancer DNS name, an instance IP, a database endpoint. They print at the end of apply, are queryable with terraform output for scripts, and can be read by other configurations through a remote state data source, which is how separate stacks share values.

They're also the interface of a module: a module returns useful values through outputs so the caller can wire them into other resources.

hcl
output "instance_ip" {
  value       = aws_instance.web.public_ip
  description = "Public IP of the web server"
}

Q11. What is a .tfvars file and when do you use one?

A .tfvars file supplies concrete values for your input variables, separating the code from environment-specific data. You keep one file per environment (dev.tfvars, prod.tfvars) and pass it with -var-file so the exact same modules deploy different sizes, counts, and regions depending on which file you point at.

terraform.tfvars and any *.auto.tfvars load automatically; named files need the flag. This is the clean way to avoid hardcoding region, sizes, or counts.

bash
terraform apply -var-file=prod.tfvars
# prod.tfvars:
# instance_type = "m5.large"
# instance_count = 4

Q12. What does terraform destroy do?

destroy removes all infrastructure Terraform manages in the current state, walking it in reverse dependency order so dependents go before their dependencies. It shows a plan of exactly what will be deleted and waits for your approval, the same safety gate as apply, and it only touches resources recorded in that state.

It's useful for tearing down temporary or test environments. To remove a single resource, target it or, more cleanly, delete its block and apply so the plan shows only that removal.

bash
terraform destroy                       # remove everything in state
terraform destroy -target=aws_instance.web   # remove one resource

Key point: Mentioning that -target is a break-glass tool, not a daily habit, indicates production maturity.

Q13. What is HCL?

HCL is HashiCorp Configuration Language, the language you write Terraform in. It's built to be both human-readable and machine-friendly: configuration is made of blocks with a type and labels, key-value arguments inside them, and expressions for references, interpolation, and simple logic like conditionals and loops over collections.

You can also write Terraform in JSON, which tools generate, but engineers author HCL because it's cleaner. HCL supports variables, functions, loops, and conditionals, enough logic to keep configs DRY without becoming a full programming language.

Q14. What do terraform fmt and terraform validate do?

fmt rewrites your files to the canonical style, with consistent indentation and alignment, so diffs stay clean and code reviews focus on real changes rather than whitespace. validate checks that the configuration is syntactically valid and internally consistent, catching bad references and type errors, without contacting any cloud or reading state.

Both are cheap and run in CI before plan. validate catches typos and bad references early; fmt keeps the whole repo formatted the same way.

bash
terraform fmt -recursive   # format every file in the tree
terraform validate         # check syntax and references

Q15. How do you pin Terraform and provider versions, and why does it matter?

You set required_version for the Terraform CLI and version constraints for each provider inside the terraform block. Operators like ~> 5.0 allow patch and minor updates but block a major bump, so an upstream release can't silently change how apply behaves across your team.

It matters because unpinned versions make plans non-reproducible: two engineers on different provider versions can get different results from the same code. The lock file then records the exact resolved versions everyone uses.

hcl
terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

Key point: The ~> pessimistic operator is the detail the key signal is: it means 'newer patches yes, next major no'.

Q16. What is a module in Terraform?

A module is a reusable, self-contained group of Terraform files that manage a related set of resources: a VPC, a database, a whole service. It takes inputs through variables, creates the resources, and returns outputs, working just like a function so you package the pattern once and reuse it across projects and environments.

Every configuration is technically the root module. You call child modules with a module block, which lets you package best-practice infrastructure once and reuse it across projects instead of copy-pasting.

hcl
module "network" {
  source     = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
  env        = "prod"
}

# use an output the module returns
resource "aws_instance" "web" {
  subnet_id = module.network.public_subnet_id
}

Key point: the question needs the function analogy: inputs in, resources built, outputs out. That framing carries into the intermediate module questions.

Q17. What is the difference between count and for_each?

Both meta-arguments create multiple instances of one resource block. count makes N copies indexed by number (0, 1, 2), so resources are addressed as aws_instance.web[0]. for_each instead iterates over a map or set, addressing each instance by a stable key like aws_instance.web["api"] rather than a position that can shift.

for_each is usually safer: removing an item from the middle of a count list re-indexes everything after it, which Terraform indicates destroy-and-recreate. Keyed for_each addresses are stable, so removing one item touches only that one.

hcl
resource "aws_instance" "web" {
  for_each      = toset(["api", "worker", "cron"])
  ami           = var.ami_id
  instance_type = "t3.micro"
  tags = { Name = each.key }
}

Key point: The re-indexing gotcha with count is the exact follow-up. Volunteering it answers the next question before it's asked.

Q18. What are Terraform workspaces?

Workspaces let one configuration hold multiple separate state files under the same backend, each tagged with its own terraform.workspace name. The starting one is called default; you create others when you want parallel copies of the same infrastructure, and you reference the current name to vary things like resource tags or counts.

They fit lightweight cases like per-developer or per-feature environments. For strongly separated environments like dev and prod, most teams prefer separate directories or state files over workspaces, because workspaces share the same code and can hide differences.

bash
terraform workspace new staging
terraform workspace select staging
terraform workspace list

Q19. What is a data source and how does it differ from a resource?

A data source reads existing information without creating or managing it: the latest AMI, an existing VPC, the current account ID, an availability zone list. A resource block, by contrast, creates and owns infrastructure, and Terraform will change or destroy it, whereas a data source is strictly read-only and never modifies anything.

Data sources let your config reference things Terraform didn't create, keeping you from hardcoding IDs that change. They're read-only and refresh on every plan.

hcl
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-*"]
  }
}

resource "aws_instance" "web" {
  ami = data.aws_ami.ubuntu.id
}

Q20. What does terraform refresh do, and how has it changed?

refresh updates the state file to match what's actually deployed, reconciling the attributes Terraform recorded with the current reality in the cloud. It's how Terraform notices drift caused by manual changes made outside its control, such as someone resizing an instance directly in the console, and reflects them in the next plan.

In modern Terraform the standalone command is deprecated in favor of a refresh that happens automatically at the start of every plan. You can skip it with -refresh=false when you want a faster plan against a large state.

Q21. How do you inspect what Terraform is currently managing?

terraform state list prints every resource address currently in state, and terraform show prints the full state or a saved plan in readable form. Together they answer 'what does Terraform think it owns?' without you opening the raw JSON state file, which is useful when a plan proposes a change you didn't expect.

For one resource, terraform state show <address> dumps its recorded attributes, handy for debugging a plan that wants to change something you didn't touch.

bash
terraform state list                 # all managed addresses
terraform state show aws_instance.web  # attributes of one resource

Q22. How does Terraform authenticate to a cloud provider?

Terraform uses the provider's normal credential chain, not a Terraform-specific mechanism. For AWS that's environment variables, a shared credentials file, an assumed IAM role, or an instance profile, checked in a set order; other providers follow the same idea with their own service-account keys or tokens configured in the provider block or environment.

The rule to state: keep credentials out of .tf files. Feed them through the environment or an assumed role so nothing sensitive lands in version control or in plan output.

bash
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_REGION="us-east-1"
terraform plan   # provider picks these up automatically

Q23. How does Terraform decide the order to create resources?

Terraform builds a dependency graph from the references between resources. When resource B uses an attribute of resource A (like a subnet id), Terraform learns B depends on A and creates A first, then B. Independent resources get created in parallel.

When there's no attribute reference but an order still matters, depends_on forces the edge explicitly. You reach for it rarely, because implicit references cover almost every case.

hcl
resource "aws_instance" "app" {
  ami        = var.ami_id
  subnet_id  = aws_subnet.private.id   # implicit dependency
  depends_on = [aws_iam_role_policy.app]  # explicit, when no reference exists
}

Q24. Which Terraform files should you keep out of version control?

Never commit the local state (terraform.tfstate and its backup), the .terraform directory of downloaded plugins, or any *.tfvars holding secrets. State can contain plaintext sensitive values like database passwords, the plugin cache is large and machine-specific, and a leaked tfvars file hands an attacker your credentials in one commit.

Do commit your .tf files and the .terraform.lock.hcl dependency lock file, so everyone resolves identical provider versions. This is a common freshers screen for basic hygiene.

text
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars
crash.log

Key point: Committing the lock file but ignoring state and .terraform is the exact split interviewers look for.

Back to question list

Terraform Intermediate Interview Questions

Intermediate21 questions

For candidates with working experience: state management, modules, provisioning logic, and the questions that separate users from understanders.

Q25. What is a remote backend and why do teams need one?

A backend defines where state lives and how operations run. The default local backend keeps state on your own disk; a remote backend (S3, Azure Blob, GCS, Terraform Cloud) stores it centrally so a whole team shares one source of truth instead of passing a state file around or each holding a divergent copy.

Teams need it for three reasons: shared state so everyone plans against the same reality, locking so two applies can't corrupt it, and durability plus encryption for a file that holds sensitive attributes. Local state has none of that.

hcl
terraform {
  backend "s3" {
    bucket         = "my-tf-state"
    key            = "prod/network.tfstate"
    region         = "us-east-1"
    dynamodb_table = "tf-lock"   # enables state locking
    encrypt        = true
  }
}

Key point: The three-part answer, shared state, locking, durability, is the bar. Naming the lock mechanism (DynamoDB for S3) shows hands-on experience.

Watch a deeper explanation

Video: Introduction to HashiCorp Terraform with Armon Dadgar (HashiCorp, an IBM Company, YouTube)

Q26. How does state locking work and why does it matter?

When Terraform starts a write operation it acquires a lock on the state so no other run can modify it at the same time. With the S3 backend that lock lives in a DynamoDB table; other backends have their own mechanism. When the operation finishes, the lock releases.

Without locking, two concurrent applies can interleave writes and corrupt state, leaving Terraform's record inconsistent with reality. If a run crashes and leaves a stale lock, terraform force-unlock releases it, carefully.

Key point: The trap is treating force-unlock as routine. Stress that it's a last resort after confirming no other apply is running.

Q27. How do you structure a reusable Terraform module?

A module is a directory with three conventional files: variables.tf for inputs, main.tf for the resources, and outputs.tf for returned values. Callers pass inputs, the module builds its resources, and outputs expose what callers need next, like an id or endpoint, so the interface stays small and the internals can change freely.

Good modules are narrow and composable: a VPC module, a database module, a service module, each with a documented interface. You avoid hardcoding provider config inside a module so callers stay in control of regions and credentials.

hcl
# modules/vpc/variables.tf
variable "cidr_block" { type = string }

# modules/vpc/main.tf
resource "aws_vpc" "this" {
  cidr_block = var.cidr_block
}

# modules/vpc/outputs.tf
output "vpc_id" { value = aws_vpc.this.id }

Q28. How do you version and source modules safely?

Modules can be sourced from a local path, a Git repository, or a registry. For anything shared, pin a version: use a Git tag or ref, or a version constraint from the registry, so an upstream change doesn't silently alter your infrastructure.

Local paths are fine inside one repo. The moment a module is shared across teams, unpinned sources become a supply-chain risk, a new commit changes what apply does without a code review on your side.

hcl
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"      # pinned from the registry
  cidr    = "10.0.0.0/16"
}

Key point: The word 'pin' is what they're listening for. Unpinned shared modules are a classic 'what could go wrong' prompt.

Q29. What kinds of built-in functions does Terraform provide?

Terraform ships functions for strings (join, split, format), collections (merge, concat, lookup, flatten), numeric work, encoding (jsonencode, base64encode), filesystem reads (file, templatefile), and IP math (cidrsubnet). There are no user-defined functions in Terraform, so you compose the built-ins together and lean on locals to The intermediate results.

They keep configs DRY: build tag maps with merge, derive subnet ranges with cidrsubnet, render user-data with templatefile. terraform console is the fastest way to test one interactively.

hcl
locals {
  common_tags = { team = "platform", env = var.env }
}

resource "aws_instance" "web" {
  subnet_id = aws_subnet.this.id
  tags      = merge(local.common_tags, { Name = "web" })
}

Q30. How do you express conditional logic in Terraform?

Terraform expresses conditional logic with the ternary form condition ? true_value : false_value for choosing a value. To create a resource only sometimes, you combine count with a condition, so count = var.enabled ? 1 : 0 makes either zero or one instance, and for_each over a possibly-empty collection does the same for keyed resources.

There's no if statement; you shape logic through these expressions, for_each on a possibly-empty collection, and dynamic blocks. Keeping it simple matters, because deeply nested ternaries get unreadable fast.

hcl
resource "aws_instance" "bastion" {
  count         = var.create_bastion ? 1 : 0
  ami           = var.ami_id
  instance_type = "t3.micro"
}

Q31. What is a dynamic block and when do you use one?

A dynamic block generates repeated nested blocks, like security group ingress rules or tag sets, from a collection, so you don't hand-write a dozen near-identical blocks. It iterates over a variable and produces one nested block per element, using iterator.value inside the content body to fill in each generated block's arguments.

Use it when the number of nested blocks depends on input. Don't overuse it: a couple of static blocks read better than a dynamic block, which trades clarity for flexibility.

hcl
resource "aws_security_group" "web" {
  name = "web"
  dynamic "ingress" {
    for_each = var.allowed_ports
    content {
      from_port   = ingress.value
      to_port     = ingress.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

Q32. What are locals and how do they differ from variables?

A local value is a named expression computed inside the configuration, referenced as local.name. Unlike input variables, callers can't set locals; they're internal helpers for values you compute once and reuse, like a naming prefix or a merged tag map.

The rule of thumb: variables are the module's external interface, locals are internal DRY helpers. Overusing locals for things that should be inputs makes a module rigid.

hcl
locals {
  name_prefix = "${var.project}-${var.env}"
}

resource "aws_s3_bucket" "logs" {
  bucket = "${local.name_prefix}-logs"
}

Q33. How do you force Terraform to recreate a single resource?

Use terraform apply -replace=<address>, which plans a destroy-and-recreate for just that one resource and leaves everything else untouched. This replaced the older terraform taint command, which separately marked a resource so the next apply would recreate it; the -replace flag folds that into a single, reviewable plan and apply step.

It's the fix when a resource is in a bad state that config alone won't correct, a corrupted instance, a manually broken service, so you rebuild it cleanly without touching anything else.

bash
terraform apply -replace=aws_instance.web   # destroy and recreate just this one

Key point: Knowing -replace superseded taint signals you keep up with current Terraform, a small but real seniority tell.

Q34. What are provisioners and why are they a last resort?

Provisioners run scripts or commands as part of a resource's creation or destruction: remote-exec runs commands on the resource itself over SSH or WinRM, and local-exec runs on the machine running Terraform. They bolt imperative steps onto a declarative tool, which is exactly why HashiCorp treats them as an escape hatch rather than a normal feature.

HashiCorp itself calls them a last resort. They break the declarative model, aren't tracked in state, and don't re-run to fix drift. Prefer cloud-init, user_data, or a dedicated config tool; reach for provisioners only when nothing else fits.

hcl
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t3.micro"
  user_data     = file("bootstrap.sh")  # preferred over remote-exec
}

Key point: Saying 'provisioners are a last resort, use user_data or cloud-init instead' is the answer graders reward here.

Q35. How do you bring existing infrastructure under Terraform?

First write configuration that matches the resource, then record it in state so Terraform stops seeing it as new. The classic path is terraform import <address> <id>; modern Terraform also supports a declarative import block you can plan and apply.

The order matters: import writes state, but you still need matching config, or the next plan will want to change the resource to match your (empty) config. Run a plan after importing to confirm it's a no-op.

hcl
import {
  to = aws_instance.web
  id = "i-0abc123def456"
}

resource "aws_instance" "web" {
  # config written to match the existing instance
}

Q36. How does Terraform handle sensitive values?

Mark a variable or output with sensitive = true and Terraform redacts it in plan and apply output, showing (sensitive value) instead of the real data so secrets don't scroll past in a terminal or a CI log. Provider attributes like generated passwords are often flagged sensitive automatically, so this propagates through expressions that use them.

The important caveat: sensitive only hides values from the CLI output. They're still stored in plaintext in the state file, so protecting the backend (encryption, access control) is the real safeguard.

hcl
variable "db_password" {
  type      = string
  sensitive = true
}

output "db_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = true
}

Key point: The follow-up is always 'but where is it stored?'. The answer, plaintext in state, is what this question is really testing.

Q37. What do terraform state mv and terraform state rm do?

state mv renames or moves a resource's address in state, for instance when you refactor a resource into a module without wanting to destroy and recreate it. state rm removes a resource from state so Terraform forgets it, without deleting the real infrastructure.

state rm is how you hand a resource off to another config or stop managing it safely. Modern code also uses a moved block in configuration to record address changes declaratively, which is safer than manual mv.

bash
terraform state mv aws_instance.web module.web.aws_instance.this
terraform state rm aws_instance.legacy   # stop managing, don't destroy

Q38. What is a moved block and why is it better than state mv?

A moved block records an address change in the configuration itself, declaring that a resource moved from an old address to a new one. On the next plan, Terraform reads it and quietly updates state to the new address instead of planning a destroy-and-recreate, which is what you'd otherwise get when refactoring into a module.

It beats manual state mv because it lives in version control, gets code-reviewed, and applies for everyone automatically. Nobody has to remember to run a CLI command out of band during a refactor.

hcl
moved {
  from = aws_instance.web
  to   = module.web.aws_instance.this
}

Q39. How can you see the dependency graph Terraform builds?

terraform graph outputs the dependency graph in DOT format, which you pipe into Graphviz to render a diagram of how resources depend on each other. It's a debugging aid when an ordering or cycle problem isn't obvious from the config.

Most of the time you don't need it, because the graph is derived from your references. But when a plan sequences things unexpectedly or reports a cycle, the visual makes the offending edge clear.

bash
terraform graph | dot -Tpng > graph.png

Q40. What are the main meta-arguments and what does each control?

Meta-arguments are special resource arguments Terraform handles itself rather than passing to the provider. count and for_each control how many instances exist; depends_on adds an explicit ordering edge; provider selects a non-default provider alias; lifecycle tunes create, update, and destroy behavior.

They apply across resource types because they shape Terraform's own behavior, not the underlying API call. Knowing which is which is a common intermediate probe.

Meta-argumentControlsCommon use
countNumber of instancesN identical copies
for_eachKeyed instancesStable per-key resources
depends_onExplicit orderingWhen no reference exists
lifecycleCreate/destroy behaviorprevent_destroy, create_before_destroy

Q41. What does the lifecycle block do?

The lifecycle block changes how Terraform treats a resource's changes. create_before_destroy builds the replacement before removing the old one to avoid downtime. prevent_destroy blocks any plan that would delete the resource. ignore_changes tells Terraform to stop tracking specific attributes that drift by design.

These solve real production problems: zero-downtime replacement, guarding a database from accidental destroy, and ignoring an attribute that an autoscaler or external system mutates.

hcl
resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = "t3.micro"
  lifecycle {
    create_before_destroy = true
    prevent_destroy       = true
    ignore_changes        = [tags["LastScanned"]]
  }
}

Key point: prevent_destroy on stateful resources (databases, buckets) is the answer to 'how do you stop an accidental delete?'.

Q42. How do you use the same provider in two regions?

Define the provider block twice, giving the second one an alias, then point specific resources or modules at the aliased configuration with the provider argument. The default block without an alias handles one region, and each aliased block handles another, so a single configuration can create resources in several regions or accounts at once.

This is how you build multi-region setups, replicate a bucket to a second region, or manage resources across accounts, all in one configuration.

hcl
provider "aws" {
  region = "us-east-1"
}
provider "aws" {
  alias  = "west"
  region = "us-west-2"
}

resource "aws_s3_bucket" "replica" {
  provider = aws.west
  bucket   = "my-replica-bucket"
}

Q43. What is terraform console useful for?

terraform console opens an interactive prompt where you evaluate expressions against your current state, variables, and locals. You test a function, inspect a resource attribute, or check what an interpolation resolves to before committing it to a config, which turns a slow plan-fix-plan loop into instant feedback on your HCL expressions.

It's the fastest feedback loop for HCL: try merge(...) or cidrsubnet(...) with real values, confirm the shape of a data source, and avoid a plan-fix-plan cycle.

bash
$ terraform console
> cidrsubnet("10.0.0.0/16", 8, 2)
"10.0.2.0/24"

Q44. What is the .terraform.lock.hcl file for?

The dependency lock file pins the exact provider versions and their checksums that init selected for your configuration. Committing it means every engineer and every CI run resolves identical providers, so a plan on your machine matches the one in the pipeline, and the checksums also guard against a tampered provider download.

Without it, a new provider release could change behavior between runs. You update it deliberately with terraform init -upgrade and The diff, treating provider bumps like any other dependency change matters.

Key point: Framing the lock file as 'reproducible provider versions, like a package lock' shows you think about supply-chain stability.

Q45. How do you manage secrets in Terraform properly?

Keep secrets out of .tf files and .tfvars in the repo. Feed them at runtime from environment variables or, better, pull them from a secret manager (Vault, AWS Secrets Manager, SSM Parameter Store) via a data source so Terraform reads the current value without storing it in code.

Then protect state, because any secret that flows through a resource lands in state as plaintext: encrypt the backend, lock down access, and avoid printing sensitive outputs. Secrets in code plus an unencrypted state is the classic finding.

hcl
data "aws_secretsmanager_secret_version" "db" {
  secret_id = "prod/db/password"
}

resource "aws_db_instance" "main" {
  password = data.aws_secretsmanager_secret_version.db.secret_string
}
Back to question list

Terraform Interview Questions for Experienced Engineers

Experienced15 questions

advanced rounds probe state architecture, drift, blast radius, and production scars. Expect every answer here to draw a follow-up.

Q46. How do you decide how to split Terraform state across a large system?

Split state to shrink blast radius and speed up plans. One giant state means every plan reads and locks everything, and a mistake can touch unrelated systems. Break it along boundaries that change independently: per environment, per team, per service, or per layer (network, data, compute).

The trade-off is coupling: split states have to share values, which you wire with remote state data sources or a tool like Terragrunt. Too fine a split creates a web of cross-state dependencies; too coarse a split creates a slow, dangerous monolith. The judgment is picking seams that change together.

Key point: how you reason about blast radius versus coupling, not a single 'right' split is the technical point.

Watch a deeper explanation

Video: Complete Terraform Course: From Beginner to Pro (DevOps Directive, YouTube)

Q47. How do you share values between separate state files?

Use the terraform_remote_state data source: one configuration exposes values through outputs, and another reads them straight from the shared backend. The network stack outputs a VPC id and subnet ids, and the app stack reads them at plan time without hardcoding, so the two stay in sync even as the network stack changes.

The senior caveat: this couples the consumer to the producer's outputs and backend location, so treat those outputs as a stable contract. Some teams prefer publishing values to a parameter store instead, which decouples the two but adds a dependency to manage.

hcl
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "my-tf-state"
    key    = "prod/network.tfstate"
    region = "us-east-1"
  }
}

resource "aws_instance" "app" {
  subnet_id = data.terraform_remote_state.network.outputs.private_subnet_id
}

Q48. How do you detect and handle configuration drift in production?

Detect drift by running a plan on a schedule against production, using terraform plan -detailed-exitcode in CI so a non-zero exit means reality diverged from your code and can trigger an alert. The refresh at the start of every plan surfaces attributes that changed outside Terraform, like a manually resized instance or an edited security group.

Handling depends on intent. If the drift is unwanted, apply to reconcile back to code. If it's a legitimate external change, update the code to match, or add ignore_changes for attributes owned by another system. The failure mode is letting drift accumulate until a routine apply proposes surprising destroys.

bash
terraform plan -detailed-exitcode
# exit 0 = no changes, 2 = drift detected, 1 = error
# wire this into a scheduled CI job to alert on drift

Key point: detailed-exitcode is the detail that signals you've automated drift detection, not just described it.

Q49. How do you run Terraform safely in CI/CD?

The pattern is plan on pull request, apply on merge, with human approval between them for anything risky. CI runs fmt and validate, produces a saved plan the reviewer reads, and applies that exact saved plan after approval so what's reviewed is what's applied.

The guardrails that matter: a remote backend with locking so pipeline runs don't race, least-privilege credentials scoped per environment, no secrets in logs, and a required approval gate on production. Auto-apply without review is where blast-radius incidents come from.

A safe Terraform CI/CD pipeline

1PR opened
run fmt, validate, and terraform plan -out=tfplan
2Review the plan
a human reads the saved plan, especially destroy lines
3Approve and merge
gate production behind a required approval
4Apply the saved plan
terraform apply tfplan so review equals what runs

Applying the exact saved plan is the safeguard: it removes the gap between what was reviewed and what executes.

Q50. How do you make infrastructure changes without downtime?

Lean on create_before_destroy so Terraform stands up the replacement before removing the old resource, and put both behind a stable indirection like a load balancer or DNS record that shifts traffic. For fleets, roll changes gradually rather than replacing everything at once.

The subtlety is that some resources can't be replaced in place cleanly (name collisions, unique constraints), so you plan the cutover: new resource up, traffic moved, old resource drained, then destroyed. Blindly changing a forces-new attribute is how a change becomes an outage.

hcl
resource "aws_launch_template" "web" {
  # ...
  lifecycle {
    create_before_destroy = true
  }
}

Q51. A state file is corrupted or lost. How do you recover?

First, stop all applies to prevent making it worse. If you have a backend with versioning (S3 versioning, Terraform Cloud history), restore the last good state, that's the fastest path and the reason versioning is non-negotiable on a state bucket.

If there's no backup, rebuild state by importing existing resources one by one to match your configuration, then plan until it's a clean no-op. It's tedious, which is exactly why the real answer is prevention: versioned, backed-up, access-controlled remote state.

Key point: the question needs to hear 'versioning and backups' first. Import-to-rebuild is the fallback, not the plan.

Q52. What problem does Terragrunt solve, and is it always needed?

Terragrunt is a wrapper that reduces repetition across many Terraform states: it keeps backend config DRY, generates provider blocks, manages dependencies between stacks, and can run commands across multiple modules at once. It grew from the pain of managing dozens of environment-by-service states.

It isn't always needed. Native Terraform now covers some of what it did (better module support, cleaner backends), so smaller setups do fine without it. The honest coverage names the specific pain it removes rather than treating it as mandatory.

Q53. How do you test Terraform code?

Layered: fmt and validate catch syntax and basic errors; static analysis (tflint, checkov, tfsec) catches misconfigurations and security issues; and the native terraform test framework (or Terratest in Go) applies real infrastructure in a throwaway account and asserts on the results.

The plan itself is a form of test in review. For modules, integration tests that actually create and destroy resources catch what static checks can't, at the cost of time and a sandbox account. Match the depth to the module's blast radius.

hcl
# tests/vpc.tftest.hcl
run "creates_vpc" {
  command = plan
  assert {
    condition     = aws_vpc.this.cidr_block == "10.0.0.0/16"
    error_message = "unexpected CIDR"
  }
}

Q54. How do you enforce guardrails on what Terraform can provision?

Policy as code: Sentinel (Terraform Cloud/Enterprise) or Open Policy Agent with conftest evaluate a plan against rules before apply, blocking things like public S3 buckets, untagged resources, or oversized instances. The check runs in the pipeline between plan and apply.

This shifts governance left, engineers get fast feedback instead of a security team review after the fact. The design point is failing the plan, not the apply, so nothing non-compliant ever reaches production.

Q55. Plans on a large configuration are slow. How do you speed them up?

Measure where the time goes first. The usual culprits are a huge single state (refresh has to poll every resource) and slow provider API calls. Splitting state into smaller, independent stacks is the biggest lever, since each plan then reads and refreshes less.

Other levers: -refresh=false when you trust state and just want a fast diff, -target to scope a run during debugging (not routine), -parallelism tuning, and cutting unnecessary data sources that hit APIs on every plan. Splitting state usually beats all the flags.

bash
terraform plan -refresh=false        # skip polling for a faster diff
terraform plan -target=module.web    # scope to one module while debugging

Q56. You have resources built with count and need to remove one from the middle. What breaks and how do you handle it?

count addresses resources by numeric index, so removing the middle item shifts every later item's index down by one. Terraform reads those shifted addresses as different resources and plans to destroy and recreate all of them, an unintended and potentially destructive churn that can take down healthy infrastructure just because one earlier item was deleted.

The fix depends on timing. Going forward, use for_each with stable keys so removals touch only the removed item. For an existing count list you must change now, use terraform state mv or moved blocks to re-map indexes to their real resources so the plan stays a no-op.

countfor_each
Address byNumeric indexMap or set key
Remove middle itemRe-indexes, recreates the restRemoves only that key
Best forTruly identical copiesNamed, independent instances
Refactor safetyNeeds state mv or movedStable across changes

Key point: This scenario is the count-versus-for_each question made concrete. Naming moved blocks as the safe refactor path is The production-ready answer.

Q57. How do you handle a major provider version upgrade across many states?

Treat it as a controlled migration, not a global bump. Read the provider's upgrade guide for breaking changes, update the version constraint in one non-production state first, run init -upgrade, and read the plan carefully, because major versions can rename attributes or change defaults.

Roll it out state by state with the lock file updated and reviewed each time, and keep a rollback path (the previous lock file) ready. Bumping every state at once is how a subtle attribute change becomes a fleet-wide surprise in the next plan.

Q58. How do you organize Terraform code across environments and teams?

Two common shapes: separate directories per environment sharing modules (dev, staging, prod each with their own state and tfvars), or a module registry that teams consume with pinned versions. Environments stay isolated so a change to dev can never accidentally touch prod state.

The principles that decide it: isolate state per environment, share logic through versioned modules not copy-paste, and keep the promotion path (dev to staging to prod) explicit. Workspaces can work for lightweight cases but blur environment boundaries when infrastructure genuinely differs.

Q59. How do you migrate state from local to a remote backend, or between backends?

Add or change the backend block, then run terraform init. Terraform detects the backend change and offers to copy existing state to the new location; you confirm, and it migrates the state file over. After migrating, verify with a plan that shows no changes.

Do it during a quiet window, back up the current state first, and make sure no one else applies mid-migration. The risk is losing or splitting state, so a backup and a clean post-migration plan are the safety checks.

bash
# after editing the backend block:
terraform init -migrate-state
terraform plan   # confirm: no changes

Q60. Design question: how would you structure Terraform for a multi-account, multi-environment organization?

Clarify scope first (how many accounts, which environments, team boundaries), then layer it: a shared set of versioned modules for common patterns, per-account and per-environment configurations each with isolated remote state and locking, and provider assume-role setups so one pipeline can target the right account with least privilege.

Add the guardrails: policy as code between plan and apply, drift detection on a schedule, and a promotion flow from lower to higher environments. Close on operational edges, blast radius per state, who can apply to production, and how secrets flow from a manager rather than code. The structure, modules plus isolated states plus guarded pipelines, is what the question scores.

Key point: This is a system-design question wearing Terraform clothes. how you reason about isolation, reuse, and guardrails, not one perfect layout is the technical point.

Back to question list

Terraform vs CloudFormation, Pulumi, and Ansible

Terraform wins when you want one declarative tool that spans many clouds and providers with a large module ecosystem. It trades the familiarity of a general-purpose language for a purpose-built configuration language and a strong state model. Where it competes: CloudFormation locks you to AWS but needs no separate state store, Pulumi lets you write infrastructure in real programming languages, and Ansible is procedural and shines at configuration management on existing servers rather than provisioning. Knowing these trade-offs out loud is itself an interview signal: it shows you pick tools on merits, not habit.

ToolApproachScopeWatch out for
TerraformDeclarative (HCL)Multi-cloud, provider pluginsState management, no built-in rollback
CloudFormationDeclarative (YAML/JSON)AWS onlyAWS lock-in, verbose templates
PulumiImperative (real languages)Multi-cloudSmaller ecosystem, language runtime needed
AnsibleProcedural (YAML playbooks)Config management, some provisioningNo first-class state, weaker drift model

How to Prepare for a Terraform Interview

Prepare in layers, and practice out loud. Most Terraform rounds move from concept questions to a live scenario (design a module, debug a broken plan) to a discussion of state and team workflow, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts until you can explain them without notes, then read one tier up for the stretch questions.
  • Write and run every snippet against a real or free-tier account; applying and destroying infrastructure cements the workflow far faster than reading.
  • Practice narrating a plan output out loud, because how you reason about changes, not just the final answer is the technical point.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical Terraform interview flow

1Recruiter or phone screen
background, cloud experience, a few concept checks
2Core concepts
state, providers, plan vs apply, modules, variables
3Live scenario
write or fix a module, read a plan, explain a change
4State and team design
remote state, locking, drift, CI/CD, blast radius

Earlier rounds increasingly run as live technical interviews. The concepts on this page are what gets probed at every stage.

Test Yourself: Terraform Quiz

Ready to test your Terraform knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which Terraform topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Are these questions enough to pass a Terraform interview?

They cover the question-answer portion well, but most Terraform rounds also include a live scenario: writing or fixing a module, or reading a plan while explaining your thinking. building small configurations out loud with a timer is the practice path. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

Which Terraform version do these answers assume?

Terraform 1.x, throughout. The 0.12 syntax jump and the state model are stable now, so answers use current HCL. If a version-specific feature matters, like moved blocks or the import block, the answer says so. When in doubt in an interview, say you're answering for Terraform 1.x.

How long does it take to prepare for a Terraform interview?

If you use Terraform at work, one to two weeks of an hour a day covers this bank with practice runs. Starting colder, plan three to four weeks and apply real configurations daily; reading answers without running plan and apply is how preparation quietly fails.

Do interviewers ask exactly these questions?

The concepts repeat far more reliably than the exact wording. Interviewers rephrase, chain follow-ups, and push toward whatever you claim confidence in. Prepare the underlying ideas, state, the dependency graph, modules, workspaces, and the phrasing takes care of itself.

Is there a way to test my Terraform knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It's the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Terraform questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 10 May 2026Last updated: 27 Jun 2026
Share: