Declarative vs Imperative
Say what, not how
Declarative states the end goal (ensure 2 app instances, port 8080 open); imperative spells the steps (create instance, open port). Cloud-native tools converge; scripts drift — that gap is the whole debate.
What you will learn
- Distinguish desired-state from step-by-step control
- Explain idempotency and convergence
- Know when imperative is still the right hammer
Imperative = verbs (run, create, delete) in order; declarative = nouns + properties (vpc {} with cidr) and the tool decides the verbs. Idempotent declarative tools converge any drift; scripts that run twice often do it wrong the second time.
What is this?
Example: 'create s3 bucket' (imperative) vs 'ensure bucket exists with policy X' (declarative). When does a server get configured → chef/ansible can be declarative-at-runtime; Terraform is declarative for provisioning; Helm declarative for K8s; kubectl apply vs create/run (hybrid). Idempotency: same input ⇒ same outcome (apply twice = no second delta). Convergence: tool detects reality != desired and repairs. Drift: manual edits or changes outside the declared model. Imperative wins: cleanup scripts, one-off migrations, local setup, boot-time config where steps matter irreversibly.
Why do you need it?
- Declarative embraces change: an env repaired to spec after someone tweaks a port.
- The plan (diff) is reviewable before any mutation — imperative scripts can't preview consequences.
- Idempotency protects retries: CI re-running failing applies doesn't double-create.
- Declarative intent survives tooling churn (rewrite engine, keep intent).
Mental model
Telling a chef 'serve chicken biryani' vs reciting the 40 recipe steps aloud
Declarative vs imperative
Declarative: chef converges to biryani and repairs if something's off. Imperative: you're responsible for step ordering and any deviation in the kitchen breaks the dish.
How it works
- Declarative tools model resources in a graph and compute the diff.
- Plans show add/change/destroy before touching anything.
- Apply executes only the delta; state records the after.
- Rerun plan → no delta ⇒ convergent — the promise of idempotency.
- Non-declarative bits (scripts) still exist where ordering/irreversibility matters.
Two styles, same goal.
Examples
Same change, both idioms
# imperative (drift-prone)aws ec2 run-instances ... ; aws ec2 authorize-security-group-ingress ... # declarative (terraform)resource "aws_instance" "web" { ami = "…"; instance_type = "t3.micro" }resource "aws_security_group_rule" "web_8080" { type = "ingress"; from_port = 8080; to_port = 8080 security_group_id = aws_security_group.web.id}# someone opens 9090 by hand → next apply closes the gap / alertsThe declarative form is a reviewable diff; the imperative form is a memo of actions that can drift.
In production
- Terraform/Pulumi/CloudFormation for all provisioning: regions, roles, network, databases.
- Helm/Jinja charts declare K8s desired state; kubectl apply converges.
- Bash/docker-entrypoint for genuinely sequential, one-shot bootstrap steps.
Best practices
- Default to declarative; reserve scripts for true bootstrapping edge cases.
- Give every imperative step an idempotency guard (guard-if-exists checks).
- Review plan diffs, not apply logs — that's where intent vs reality is decided.
- Kill the habit of 'quick console edit'; make drift a hard alarm not a whoops.
Common mistakes
- Script that assumes clean state (create fails because 'already exists' on retry).
- Declarative tool used imperatively (terraform apply -target everything, plan by ceremony).
- Mixing styles without discipline: a manual change that future plans silently re-apply.
- Believing declarative removes the need for state; it centralizes it.
Debugging tips
- Apply timing out but reality exists → verify with query; often a permissions/state hiccup, not the resource.
- Converge hammering a resource each run → attributes changing in a non-unique way (ForceNew).
- Plan shows changes you didn't intend → last console edit is the smell; revert it or codify it.
- Compare plan across runs to isolate what returns to the same diff over and over.
Interview checkpoint
Quick quiz
A declarative tool's plan output enables:
You need idempotency primarily because: