Declarative vs Imperative IaC
Every Infrastructure as Code tool falls somewhere on a spectrum between two philosophies: declarative and imperative. A declarative tool asks you to describe what you want the end result to look like — 'there should be a web server with this configuration' — and the tool itself figures out the sequence of API calls needed to get there, whether that means creating something new, updating an existing resource in place, or leaving it untouched because it already matches. An imperative tool asks you to describe how to get there — a specific ordered list of steps, like 'create a VPC, then create a subnet inside it, then launch an instance in that subnet.'
Cricket analogy: A declarative captain like Virat Kohli tells the team 'chase 180 by over 20' and lets batters decide the approach, while an imperative coach scripts each over: hit fours in over 1, singles in over 2, exactly like specifying VPC-then-subnet-then-instance steps.
Terraform's Declarative Model
Terraform is fundamentally declarative. You write .tf files describing resources, and Terraform's engine computes a dependency graph and an execution plan on every run. If you run terraform apply twice in a row with no configuration changes, the second run does nothing, because the actual infrastructure already matches the desired state — this property is called idempotency. Declarative tools are generally easier to reason about at scale because the configuration is a single source of truth for 'what should exist', rather than a log of historical actions that must be replayed correctly.
Cricket analogy: Terraform's idempotency is like Sachin Tendulkar's pre-ball routine — running it twice before facing the same delivery produces the identical stance, not a doubled-up ritual, because the routine is already fully executed.
Imperative Approaches
Imperative tools and scripts — for example, a Bash script calling the AWS CLI step by step, or older-style Chef recipes — explicitly encode the sequence of operations. This gives fine-grained control over ordering and can be simpler for one-off or highly custom tasks, but it pushes the burden of idempotency onto the script author: if the script is re-run, it must itself check 'does this resource already exist?' before creating it, or it will error out or create duplicates. As infrastructure grows, hand-maintaining that logic across many resources becomes increasingly fragile.
Cricket analogy: A bowler like Jasprit Bumrah manually re-checking the field placement before every single delivery, rather than trusting a standing plan, mirrors a Bash script that must itself verify 'does this resource exist' before each AWS CLI call.
A useful analogy: declarative is like giving a GPS a destination address and letting it compute the route, recalculating automatically if you take a wrong turn. Imperative is like memorizing turn-by-turn directions -- precise, but if something changes along the way (a road closure, a wrong turn), the directions no longer apply and must be manually recalculated.
Where the Line Blurs
The distinction is not always crisp. Ansible, often called a configuration management tool, supports both styles: many of its modules are declarative ('ensure this package is installed') even though playbooks execute as an ordered list of tasks. Terraform itself has imperative-feeling escape hatches, such as provisioners, which run arbitrary scripts as part of resource creation — a practice generally discouraged because it breaks the clean declarative model and reintroduces the ordering and idempotency problems imperative approaches face.
Cricket analogy: Ansible is like a hybrid batting order where each batter is told 'ensure 40 runs are on the board' (declarative) but must still walk in during a fixed batting order (imperative), while Terraform provisioners are like a captain occasionally sending a runner onto the field mid-over — allowed but frowned upon.
Relying heavily on Terraform provisioners (local-exec, remote-exec) to patch over gaps in declarative resource support is a common anti-pattern. It reintroduces imperative fragility -- failures mid-script can leave infrastructure in an inconsistent state that Terraform's plan/apply model cannot cleanly reconcile.
# Declarative (Terraform): describe the desired end state.
# Terraform decides HOW to reach it, and is safe to re-apply.
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}# Imperative (shell script): describe the exact steps to run.
# Re-running this naively would try to create a duplicate instance
# unless the script author adds explicit existence checks.
aws ec2 run-instances \
--image-id ami-0c55b159cbfafe1f0 \
--instance-type t3.micro \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=web-server}]'- Declarative IaC describes desired end state; the tool computes the steps to reach it.
- Imperative IaC describes the exact sequence of steps/commands to execute.
- Terraform is declarative and idempotent: re-applying unchanged config makes no changes.
- Imperative scripts must manually implement idempotency checks (existence checks before create).
- Declarative configuration scales better as a 'single source of truth' for large infrastructures.
- Terraform provisioners are an imperative escape hatch and are generally discouraged when a declarative resource exists.
Practice what you learned
1. In declarative IaC, what does the engineer primarily write?
2. What does 'idempotency' mean in the context of IaC?
3. Why do imperative scripts typically require manual existence checks?
4. Which Terraform feature is considered an imperative escape hatch and is generally discouraged?
5. Which analogy best captures the difference between declarative and imperative approaches?
Was this page helpful?
You May Also Like
What Is Infrastructure as Code?
Infrastructure as Code (IaC) is the practice of defining and provisioning computing infrastructure through machine-readable configuration files instead of manual processes.
What Is Terraform?
Terraform is HashiCorp's open-source Infrastructure as Code tool that uses a declarative language, HCL, to provision and manage resources across hundreds of cloud and SaaS providers.
The Dependency Graph
Terraform builds a directed acyclic graph of every resource before executing, determining safe, maximally parallel ordering for creates, updates, and deletes.
Provisioners and When to Avoid Them
Understand Terraform's provisioner blocks for running scripts on resource creation, why HashiCorp considers them a last resort, and what to use instead.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics