Terraform interviews test your understanding of infrastructure as code, HCL syntax, state management, modules, providers, workspaces, and production patterns. This guide covers the 50 most common questions — with concise answers and real examples.
Quick Reference
| Topic | Key Concepts |
|---|---|
| Core | providers, resources, data sources, variables, outputs |
| State | terraform.tfstate, remote backend, locking |
| Modules | reusable code, source, input/output variables |
| Workflow | init → plan → apply → destroy |
| Workspaces | isolated state environments in the same config |
| Meta-arguments | count, for_each, depends_on, lifecycle |
Core Concepts
1. What is Terraform and what problem does it solve?
Terraform is an open-source Infrastructure as Code (IaC) tool by HashiCorp. It lets you define cloud infrastructure in human-readable HCL (HashiCorp Configuration Language) files and manage its full lifecycle — create, update, destroy — across multiple providers.
Problems it solves:
- Manual, error-prone cloud console clicks → reproducible code
- Inconsistent environments → idempotent configuration
- No audit trail → version-controlled infrastructure
- Vendor lock-in → multi-cloud with a single tool
2. What is HCL?
HCL (HashiCorp Configuration Language) is the declarative language used to write Terraform configurations. It's designed to be human-readable and supports strings, numbers, booleans, lists, maps, and expressions.
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
Env = var.environment
}
}
3. What is a Terraform provider?
A provider is a plugin that lets Terraform manage resources for a specific platform (AWS, Azure, GCP, GitHub, Kubernetes, etc.). Providers must be declared in the configuration and downloaded via terraform init.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
4. What is a Terraform resource?
A resource is the most important element in Terraform — it represents a single infrastructure object (VM, bucket, DNS record, etc.). The syntax is resource "<TYPE>" "<NAME>".
resource "aws_s3_bucket" "logs" {
bucket = "my-app-logs-2025"
}
5. What is a data source?
A data source lets you read existing infrastructure that wasn't created by your current Terraform config. Use it to reference resources managed elsewhere.
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-*-22.04-amd64-server-*"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
# ...
}
6. What are Terraform variables?
Variables make configurations reusable. Types: string, number, bool, list, map, object, tuple, set.
variable "instance_type" {
type = string
description = "EC2 instance type"
default = "t3.micro"
}
# Usage
resource "aws_instance" "web" {
instance_type = var.instance_type
}
Passing values: CLI (-var), .tfvars file, environment variables (TF_VAR_instance_type).
7. What are outputs?
Outputs expose values after apply — useful for passing data between modules or displaying information to users.
output "instance_ip" {
description = "Public IP of the web instance"
value = aws_instance.web.public_ip
}
8. Explain the Terraform workflow.
| Command | Description |
|---|---|
terraform init |
Download providers and modules, initialise backend |
terraform validate |
Check syntax without contacting providers |
terraform fmt |
Format code to canonical style |
terraform plan |
Preview changes (diff) |
terraform apply |
Apply changes to real infrastructure |
terraform destroy |
Destroy all managed resources |
terraform output |
Show output values |
terraform show |
Show current state or a plan file |
terraform import |
Bring existing resource under Terraform management |
terraform taint (deprecated) → terraform apply -replace |
Force recreation of a resource |
State Management
9. What is Terraform state?
State is a JSON file (terraform.tfstate) that maps your Terraform configuration to real-world infrastructure. Terraform uses state to:
- Track which resources it manages
- Determine what changes are needed (plan diff)
- Store metadata (resource IDs, dependencies)
Default location: local terraform.tfstate file.
Production: always use a remote backend with state locking.
10. What is a remote backend and why use one?
A remote backend stores state in a shared, persistent location instead of the local filesystem.
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks" # for state locking
encrypt = true
}
}
Why remote?
- Team collaboration — everyone reads the same state
- State locking — prevents concurrent applies
- Versioning and encryption — auditability and security
Common backends: S3+DynamoDB (AWS), GCS (GCP), Azure Blob, Terraform Cloud/Enterprise.
11. What is state locking?
State locking prevents two operations from modifying state simultaneously, which would corrupt it. With the S3 backend, DynamoDB provides locking. Terraform Cloud locks automatically.
If a lock is stuck (e.g., after a crash): terraform force-unlock <LOCK_ID>.
12. What is terraform.tfstate.backup?
Terraform creates a .backup file before each state write as a safeguard. It contains the previous version of state.
13. What is terraform state and common sub-commands?
The terraform state command lets you inspect and manipulate state directly.
| Command | Description |
|---|---|
terraform state list |
List all resources in state |
terraform state show <resource> |
Show attributes of a resource |
terraform state mv |
Move/rename a resource in state |
terraform state rm |
Remove a resource from state (without destroying it) |
terraform state pull |
Download and print remote state |
terraform state push |
Upload local state to remote backend |
14. What does terraform import do?
terraform import brings an existing (manually created) resource under Terraform management by adding it to state. You still need to write the HCL resource block manually.
# Import existing EC2 instance
terraform import aws_instance.web i-1234567890abcdef0
15. What is the difference between terraform plan -out and terraform apply?
terraform plan -out=tfplan saves the plan to a binary file. terraform apply tfplan applies exactly that saved plan — no interactive confirmation, no drift recalculation. This is the recommended pattern for CI/CD pipelines.
Modules
16. What is a Terraform module?
A module is a container for multiple resources used together. Every Terraform config is technically a module (the root module). Child modules are reusable code called from the root or other modules.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.2"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
}
17. What are module sources?
| Source | Example |
|---|---|
| Local path | source = "./modules/vpc" |
| Terraform Registry | source = "hashicorp/consul/aws" |
| GitHub | source = "github.com/org/repo" |
| S3 bucket | source = "s3::https://..." |
| Git URL | source = "git::https://..." |
18. How do you pass data in and out of a module?
- Input: declare
variableblocks inside the module, pass values frommoduleblock - Output: declare
outputblocks inside the module, reference asmodule.<NAME>.<OUTPUT>
# modules/ec2/main.tf
variable "instance_type" {}
output "public_ip" { value = aws_instance.this.public_ip }
# root/main.tf
module "web" {
source = "./modules/ec2"
instance_type = "t3.small"
}
output "web_ip" { value = module.web.public_ip }
19. What is the Terraform Registry?
The public registry at registry.terraform.io hosts community and HashiCorp-verified providers and modules. You can also use a private registry (Terraform Cloud/Enterprise).
20. Why should you pin module and provider versions?
Without version pinning, terraform init fetches the latest version, which can introduce breaking changes. Always pin:
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
required_version = ">= 1.6"
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.2"
}
Meta-Arguments
21. What is count?
count creates multiple instances of a resource. Access each with <resource>[index].
resource "aws_instance" "web" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = { Name = "web-${count.index}" }
}
22. What is for_each?
for_each creates instances from a map or set. Preferred over count when resources have distinct identities — avoids index-shift problems.
variable "buckets" {
default = { logs = "eu-west-1", backups = "us-east-1" }
}
resource "aws_s3_bucket" "this" {
for_each = var.buckets
bucket = "myapp-${each.key}"
# each.key = "logs", each.value = "eu-west-1"
}
23. When to use count vs for_each?
count |
for_each |
|
|---|---|---|
| Input type | integer | map or set of strings |
| Reference | resource[0], resource[1] |
resource["key"] |
| Deletion risk | Re-index shifts resources | Only the keyed resource affected |
| Best for | identical resources | resources with distinct names/configs |
24. What is depends_on?
Terraform infers most dependencies automatically from references. depends_on is for hidden dependencies not expressed through references (e.g., IAM policy must be attached before an EC2 instance profile is used).
resource "aws_instance" "app" {
# ...
depends_on = [aws_iam_role_policy_attachment.this]
}
25. What is the lifecycle block?
lifecycle customises resource behaviour:
resource "aws_instance" "web" {
# ...
lifecycle {
create_before_destroy = true # zero-downtime replacement
prevent_destroy = true # block accidental destroy
ignore_changes = [tags] # ignore drift on these attrs
replace_triggered_by = [aws_ami.this.id] # re-create on AMI change
}
}
Workspaces
26. What are Terraform workspaces?
Workspaces allow multiple isolated state files within the same configuration directory. Each workspace has its own terraform.tfstate.
terraform workspace new staging
terraform workspace select prod
terraform workspace list
Use terraform.workspace in code to branch logic:
locals {
instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"
}
27. What are the limitations of workspaces?
- All workspaces share the same backend configuration
- Not ideal for fundamentally different infrastructure (use separate root modules instead)
- Terraform Cloud workspaces are richer than CLI workspaces (different repos, different vars, VCS-driven runs)
Expressions and Functions
28. What are locals?
locals are computed values that avoid repetition. Unlike variables, they cannot be set from outside.
locals {
name_prefix = "${var.project}-${var.environment}"
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "Terraform"
}
}
resource "aws_vpc" "main" {
tags = merge(local.common_tags, { Name = "${local.name_prefix}-vpc" })
}
29. What are commonly used Terraform built-in functions?
| Category | Functions |
|---|---|
| String | format, join, split, replace, trimspace, lower, upper |
| Collection | length, merge, concat, flatten, toset, tolist, tomap, lookup, keys, values |
| Numeric | max, min, ceil, floor, abs |
| Encoding | jsonencode, jsondecode, base64encode, base64decode |
| Filesystem | file, templatefile |
| IP network | cidrsubnet, cidrhost |
| Type | tostring, tonumber, tobool |
30. What is a dynamic block?
dynamic generates repeated nested blocks (e.g., security group ingress rules) from a collection.
variable "ingress_rules" {
default = [
{ port = 80, cidr = "0.0.0.0/0" },
{ port = 443, cidr = "0.0.0.0/0" },
]
}
resource "aws_security_group" "web" {
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.port
to_port = ingress.value.port
protocol = "tcp"
cidr_blocks = [ingress.value.cidr]
}
}
}
31. What is templatefile?
templatefile renders a template file with given variables — useful for user-data scripts.
resource "aws_instance" "web" {
user_data = templatefile("${path.module}/user_data.sh.tpl", {
db_host = aws_db_instance.main.address
app_env = var.environment
})
}
32. What are conditional expressions?
# condition ? true_val : false_val
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
# Conditional resource creation with count
resource "aws_eip" "nat" {
count = var.enable_nat ? 1 : 0
}
Security and Best Practices
33. How do you manage secrets in Terraform?
Never hardcode secrets. Options:
| Method | How |
|---|---|
| Environment variables | TF_VAR_db_password=... |
.tfvars file (gitignored) |
db_password = "..." |
| AWS Secrets Manager data source | data "aws_secretsmanager_secret_version" |
| HashiCorp Vault provider | data "vault_generic_secret" |
| Terraform Cloud variables | Sensitive variable marked "sensitive" |
Mark sensitive outputs to prevent display:
output "db_password" {
value = random_password.db.result
sensitive = true
}
34. What is sensitive = true in variables?
Marking a variable sensitive prevents its value from appearing in plan/apply output and logs.
variable "db_password" {
type = string
sensitive = true
}
35. How do you prevent accidental resource deletion?
lifecycle { prevent_destroy = true }in the resource block- Terraform Cloud policy-as-code (Sentinel / OPA)
- IAM policies that restrict the deploying principal
Advanced Topics
36. What is terraform refresh / -refresh-only?
terraform refresh (deprecated in favour of terraform apply -refresh-only) updates state to match real-world infrastructure without making changes. Useful for detecting drift.
terraform apply -refresh-only
37. What is drift?
Drift occurs when real infrastructure diverges from the Terraform state (e.g., someone manually changed an EC2 instance type via the console). terraform plan detects drift and shows what changes would bring infrastructure back to the desired state.
38. What is terraform taint / -replace?
Forces recreation of a specific resource on next apply. Since Terraform 0.15.2, preferred syntax:
terraform apply -replace="aws_instance.web"
39. What is terraform graph?
Outputs a DOT-format dependency graph of the current config. Visualise with Graphviz:
terraform graph | dot -Tsvg > graph.svg
40. What is the .terraform.lock.hcl file?
The dependency lock file records the exact provider versions and checksums used by terraform init. Commit this to version control so all team members and CI use identical provider versions.
41. What are Terraform provisioners?
Provisioners run local or remote scripts on resources after creation. They are a last resort — prefer cloud-native init mechanisms (EC2 user-data, Azure custom script extension, etc.).
resource "aws_instance" "web" {
# ...
provisioner "remote-exec" {
inline = ["sudo apt-get install -y nginx"]
connection {
type = "ssh"
user = "ubuntu"
private_key = file("~/.ssh/id_rsa")
host = self.public_ip
}
}
}
Problems with provisioners: failures leave resources in unknown state; not idempotent; violate immutable infrastructure principle.
42. What is a null resource and when would you use it?
null_resource has no infrastructure representation but can run provisioners or act as a dependency anchor. Replaced in modern Terraform by terraform_data (v1.4+).
resource "null_resource" "run_script" {
triggers = { always = timestamp() }
provisioner "local-exec" { command = "echo done" }
}
43. What is moved block?
The moved block tells Terraform that a resource was renamed/moved, so it updates state without destroying and recreating.
moved {
from = aws_instance.old_name
to = aws_instance.new_name
}
44. What is Terraform Cloud vs Terraform Enterprise?
| Terraform Cloud | Terraform Enterprise | |
|---|---|---|
| Hosting | SaaS (HashiCorp) | Self-hosted |
| Pricing | Free tier + paid plans | License fee |
| Features | Remote runs, state, registry, Sentinel | All Cloud features + SSO, audit, clustering |
| Best for | Teams without infra restrictions | Regulated enterprises |
CI/CD and Workflows
45. How do you use Terraform in a CI/CD pipeline?
Recommended pattern:
# GitHub Actions example
jobs:
terraform:
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform validate
- run: terraform plan -out=tfplan
- run: terraform apply -auto-approve tfplan
if: github.ref == 'refs/heads/main'
Key practices:
- Store state in remote backend (not the runner)
- Use OIDC / short-lived credentials (no long-lived AWS keys in CI)
- Plan on PR, apply on merge to main
- Post plan output as PR comment
46. What is the Atlantis pattern?
Atlantis is an open-source tool that runs terraform plan on PRs and terraform apply when you comment atlantis apply. It keeps apply tied to reviewed, merged code without giving CI broad permissions.
47. Describe a blue-green deployment with Terraform.
# Create new (green) before destroying old (blue)
resource "aws_autoscaling_group" "app" {
lifecycle { create_before_destroy = true }
}
# Shift traffic via ALB target group weight
resource "aws_lb_listener_rule" "blue_green" {
# forward to green TG at 100% after validation
}
Comparison
48. Terraform vs CloudFormation vs Pulumi
| Feature | Terraform | CloudFormation | Pulumi |
|---|---|---|---|
| Language | HCL | JSON/YAML | Python, TS, Go, C# |
| Multi-cloud | Yes (1000+ providers) | AWS only | Yes |
| State management | Self-managed or TF Cloud | AWS-managed | Self or Pulumi Cloud |
| Import existing | terraform import |
CloudFormation import | pulumi import |
| Drift detection | terraform plan |
Stack drift detection | pulumi preview |
| Community modules | Terraform Registry | No equivalent | Pulumi Registry |
| Maturity | Very high | Very high | Growing |
| Best for | Multi-cloud IaC | AWS-only teams | Devs preferring real languages |
49. Terraform vs Ansible
| Terraform | Ansible | |
|---|---|---|
| Primary use | Infrastructure provisioning | Configuration management |
| State | Stateful | Stateless (idempotent plays) |
| Language | HCL declarative | YAML imperative/procedural |
| Cloud resources | Native | Via modules (less first-class) |
| OS config | Limited | Excellent |
| Combined use | Terraform provisions → Ansible configures |
50. Common Terraform Anti-Patterns
| Anti-pattern | Problem | Fix |
|---|---|---|
| Local state in team environment | Conflicts, no locking | Remote backend + locking |
terraform apply in CI without saved plan |
Plan may differ from what was reviewed | plan -out, then apply tfplan |
| Giant root module (all infra in one file) | Blast radius, slow plans | Split into environments + modules |
| Hardcoded secrets | Security exposure | Secrets manager data sources |
| Unpinned provider versions | Breaking changes on init |
version = "~> 5.0" constraint |
count for named resources |
Index shift deletes the wrong resource | for_each with meaningful keys |
| Manual console changes | Drift, conflicts on next apply | Enforce IaC-only changes via SCPs |
| No state backup/versioning | Unrecoverable state corruption | S3 versioning + DynamoDB lock |
Common Mistakes
| Mistake | What Happens | Fix |
|---|---|---|
Deleting a resource block without state rm |
Terraform destroys the resource | terraform state rm first if not destroying |
Running apply without plan review |
Unexpected changes deployed | Always review plan, use -out in CI |
Using terraform.workspace for env isolation alone |
Shared backend can confuse | Use separate backends or directories per env |
Forgetting terraform init after adding provider |
"Provider not found" error | Always init after config changes |
| Modifying state manually | State corruption | Use terraform state sub-commands |
for_each with a list (not a set/map) |
Type error | Convert: for_each = toset(var.list) |
| Circular dependencies | Error: cycle | Refactor using depends_on or restructure resources |
| Large blast radius in single workspace | One mistake destroys everything | Split by environment and service |
Terraform vs Related Tools
| Tool | Category | Relation to Terraform |
|---|---|---|
| Ansible | Config mgmt | Complementary — Terraform provisions, Ansible configures |
| CloudFormation | IaC | AWS-only alternative |
| Pulumi | IaC | Alternative with real programming languages |
| AWS CDK | IaC | AWS-only, code-first (TS/Python/Java) |
| Terragrunt | Terraform wrapper | Adds DRY config, remote state helpers |
| Atlantis | GitOps for Terraform | Automates plan/apply via PR comments |
| Checkov / tfsec | Security scanner | Scans HCL for misconfigurations |
| Infracost | Cost estimation | Shows terraform plan cost impact |
FAQ
Q: How does Terraform know which resources to update?
A: It computes a diff between desired state (your HCL) and current state (tfstate). Only resources with differences are touched.
Q: Can Terraform manage resources it didn't create?
A: Yes — use terraform import to bring existing resources into state, then write the matching HCL block.
Q: What happens if two people run terraform apply simultaneously?
A: With a remote backend + locking (DynamoDB or Terraform Cloud), the second operation waits or errors with "state locked". Without locking, state corruption is possible.
Q: What is the difference between terraform destroy and removing all resource blocks?
A: Both destroy resources, but terraform destroy is explicit and safer. Removing resource blocks and running apply has the same effect but is less obvious to reviewers.
Q: How do you handle multi-region or multi-account deployments?
A: Use provider aliases for multi-region, and assume_role in the AWS provider for cross-account. Each environment gets its own remote state key/path.
provider "aws" { alias = "eu"; region = "eu-west-1" }
resource "aws_s3_bucket" "eu_logs" {
provider = aws.eu
bucket = "my-eu-logs"
}
Q: What is Sentinel in Terraform?
A: Sentinel is HashiCorp's policy-as-code framework (Terraform Cloud/Enterprise). It lets you write policies that block apply if infrastructure doesn't meet compliance rules (e.g., all S3 buckets must have encryption, no public EC2 instances).