100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
HCL

Declarative vs Imperative IaC

Declarative IaC describes the desired end state and lets the tool compute the steps, while imperative IaC specifies the exact sequence of actions to perform; each model carries distinct tradeoffs.

IaC FoundationsBeginner7 min readJul 9, 2026
Analogies

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.

hcl
# 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"
  }
}
bash
# 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

Was this page helpful?

Topics covered

#HCL#TerraformInfrastructureAsCodeStudyNotes#DevOps#DeclarativeVsImperativeIaC#Declarative#Imperative#IaC#Terraform#StudyNotes#SkillVeris