Explain declarative vs. imperative infrastructure provisioning, and where Terraform fits.
Quick Answer
Imperative tooling (a bash script, boto3 script) specifies the steps to reach a result. You must handle ordering, idempotency, and error recovery yourself. Declarative tooling (Terraform, Kubernetes manifests) specifies the desired end state, and the tool's engine diffs that against reality and figures out the steps. Terraform is declarative: it builds a dependency graph from resource references, computes create/update/destroy actions via plan, and applies them idempotently. Running apply twice in a row with no config change is a no-op.
Detailed Answer
This distinction comes up constantly when comparing Terraform to shell scripts, SDK-based automation, or CloudFormation, so it's worth having a crisp mental model.
Imperative: specify the steps
An imperative script tells the system exactly how to reach a result, step by step:
aws ec2 run-instances --image-id ami-123 --count 1 --instance-type t3.micro
aws ec2 create-tags --resources i-abcdef --tags Key=Name,Value=web
Problems with this approach at scale:
- You must handle idempotency yourself — running the script twice creates two instances, not one.
- You must handle ordering and error recovery — what happens if step 2 fails after step 1 succeeded?
- There's no built-in way to know what the script has already created without re-reading external state.
Declarative: specify the desired end state
resource "aws_instance" "web" {
ami = "ami-123"
instance_type = "t3.micro"
tags = { Name = "web" }
}
You describe what should exist, not the steps to get there. The engine (Terraform Core) is responsible for:
- Comparing desired state (config) against last-known state (the state file) and real infrastructure.
- Computing the minimal set of create/update/destroy actions needed.
- Executing them in correct dependency order.
- Being idempotent by construction — running
applyagain with no config changes is a no-op, because the diff comes back empty.
Where Terraform sits
Terraform is fully declarative at the resource level: you never write "create this, then that." Its dependency graph, built automatically from resource references, determines execution order, and its plan/apply separation means the "what will change" computation is always visible before anything is touched. This is precisely what makes terraform plan meaningful as a review step. There's a clear notion of "current state" vs. "desired state" to diff, which a purely imperative script doesn't have.
Interview framing: declarative tools answer "what do you want?"; imperative tools answer "what should I do?" Terraform deliberately chose the former to get safe, idempotent, reviewable infrastructure changes.