ArchitectMind
  1. Home
  2. Tutorials
  3. Book 9 · Cloud, Containers, DevOps & Production Architecture
  4. Volume IV
  5. Declarative vs Imperative
Book 9 · Volume IV — Infrastructure as Code & Platform Engineering · Topic 1.2

Declarative vs Imperative

Describing what you want versus how to get there

11 min readStage 4 · Production & architecture10 interview questions
Chapter
Chapter 1 — Infrastructure as Code
Prerequisites
Topic 1.1
You will be able to
Distinguish declarative and imperative approaches precisely, explain how declarative tools compute and converge changes, recognise where imperative steps remain necessary, combine both safely, and choose between push-based and reconciling models
Covers
Declarative vs imperative
On this page
  1. 1. Concept / Definition
  2. 2. Why It Exists
  3. 3. Core Concepts — Declarative vs Imperative
  4. 4. How It Works Internally — Convergence
  5. 4A. Deep Dive — Same Change, Two Approaches
  6. 4B. Deep Dive — Where Imperative Still Fits
  7. 4C. Deep Dive — Push vs Pull Reconciliation
  8. 5. APIs / Features / Syntax
  9. 6. Practical Examples
  10. 7. Common Problems & Mistakes
  11. 8. Performance & Production Considerations
  12. 9. Architecture & Design Considerations
  13. 10. Interview Questions & Answers

1Concept / Definition

A declarative approach describes the desired end state ("there should be a VPC with three private subnets") and lets a tool work out what to create, change or delete. An imperative approach describes the sequence of actions ("create a VPC, then create subnet A, then …"). Declarative tools are idempotent and self-correcting by design; imperative scripts must handle ordering, existing state and failures themselves.

Declarative vs imperative
Figure 1 — Declarative vs imperative
How declarative tools converge
Figure 2 — How declarative tools converge
VocabularyMeaning
Desired stateWhat the code declares
ConvergenceMoving actual state toward desired
Reconciliation loopContinuous convergence by a controller
Partial applySome changes succeeded before an error
Table 1 — Terms

2Why It Exists

Imperative painDeclarative benefit
Scripts fail when resources already existDiff-based changes
Ordering bugsDependency graph
No previewPlan
Cleanup forgottenRemoval from code deletes resource
Table 2 — Why

3Core Concepts — Declarative vs Imperative

AspectDeclarativeImperative
Author writesEnd stateSteps
OrderingComputed from dependenciesManual
Re-runNo-op if convergedMay duplicate/fail
DeletionRemove from codeWrite delete steps
PreviewPlan/change setDry-run if supported
FlexibilityConstrained by modelUnlimited
Table 3 — Comparison
Spectrum of approaches
Figure 3 — Spectrum of approaches

4How It Works Internally — Convergence

StepTerraformKubernetes/Crossplane
Read desiredHCLCustom resource spec
Read actualState + provider refreshWatch cloud resources
DiffPlanController compares
ActApply on command (push)Continuously (pull)
RecordState filestatus field
Table 4 — Two declarative models

4ADeep Dive — Same Change, Two Approaches

Task: add a third private subnetDeclarative (Terraform)Imperative (script)
Code changeAdd AZ to listNew create-subnet call with CIDR logic
Existing subnetsUntouched automaticallyMust check they exist
Route table associationsDerived from for_eachSeparate steps
Re-run safetyYesMust guard every step
Review artefact"1 to add, 0 to change"Script diff (behaviour unclear)
Table 5 — Declarative scales with complexity

4BDeep Dive — Where Imperative Still Fits

TaskWhy imperativeHow to contain it
Data migrationsOrdered, one-timeVersioned migration tools (Flyway)
Bootstrap (state bucket, first role)Chicken-and-eggSmall documented script, then import
Operational actions (failover, restart)Actions, not stateRunbooks / SSM Automation
Glue between systemsSequencingPipeline steps calling declarative tools
Complex computed valuesLogicCDK/Pulumi generate declarative output
Table 6 — Keep imperative at the edges

4CDeep Dive — Push vs Pull Reconciliation

ModelExamplesProsCons
Push (apply on demand)Terraform in CIExplicit plans, clear change pointsDrift until next apply
Pull (continuous)Crossplane, ACK, Argo CDSelf-healingHarder previews, controller complexity
ShopNow choicePush for AWS infra; pull (Argo CD) for Kubernetes appsBest of both—
Table 7 — Reconciliation models

5APIs / Features / Syntax

HCL
# Declarative: subnets derived from a map; adding an entry adds a subnet
variable "private_subnets" {
  type = map(object({ az = string, cidr = string }))
  default = {
    a = { az = "ap-south-1a", cidr = "10.20.16.0/20" }
    b = { az = "ap-south-1b", cidr = "10.20.32.0/20" }
    c = { az = "ap-south-1c", cidr = "10.20.48.0/20" }
  }
}
resource "aws_subnet" "private" {
  for_each          = var.private_subnets
  vpc_id            = aws_vpc.main.id
  availability_zone = each.value.az
  cidr_block        = each.value.cidr
}
BASH
# Imperative equivalent (fragile): must check existence, order and failures
for az in a b c; do
  exists=$(aws ec2 describe-subnets --filters Name=tag:Name,Values=private-$az \
           --query 'length(Subnets)' --output text)
  [ "$exists" = "0" ] && aws ec2 create-subnet --vpc-id "$VPC" \
      --cidr-block "$(cidr_for $az)" --availability-zone "ap-south-1$az"
done
TYPESCRIPT
// CDK: imperative code that synthesises a declarative CloudFormation template
const vpc = new ec2.Vpc(this, 'Main', {
  maxAzs: 3,
  subnetConfiguration: [{
    name: 'private', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 20,
  }],
});

6Practical Examples

6.1 Script re-run created duplicates

A bash provisioning script re-run after a timeout created a second NAT gateway and a duplicate route table. The Terraform replacement had no such issue: re-apply was a no-op.

6.2 Forgotten resources

Imperative scripts created test queues that were never deleted (no delete path). Declarative definitions removed them when the code was removed; SQS cost dropped 12%.

6.3 CDK for complex logic

A team generating 200 similar IAM roles from a service catalogue used CDK loops in TypeScript — imperative authoring, declarative deployment via CloudFormation.

6.4 Worked example — idempotency test

RunDeclarativeImperative
1CreatesCreates
2No changesErrors or duplicates (unless guarded)
Table 8 — Re-runs reveal the difference

6.5 Worked example — deletion semantics

ActionDeclarative
Remove resource blockPlan shows destroy
Guardprevent_destroy for critical resources
Table 9 — Deletion by omission — review carefully

6.6 Worked example — ordering

DependencyDeclarative handling
Subnet needs VPC IDReference creates edge
IAM policy before role useReference or depends_on
Table 10 — Graph instead of manual order

6.7 Worked example — interview whiteboard

PromptAnswer outline
"Declarative or imperative infrastructure?""Declarative describes desired state; the tool reads actual state, computes a diff (plan) and converges, making runs idempotent, ordered by dependency graphs and reviewable. Imperative describes steps; flexible but you own ordering, existence checks, partial failures and cleanup. Use declarative (Terraform/CloudFormation, or CDK/Pulumi generating declarative output) for infrastructure; keep imperative for migrations, bootstrap, operational actions and glue. Push (CI apply) vs pull (controllers) is a second axis."
Table 11 — Structured answer

6.8 Worked example — PayGrid preference

RuleDetail
No imperative changes to CDE infrastructureOnly reviewed plans
Operational actions via approved SSM runbooksAudited
Table 12 — Controlled imperative actions

6.9 Worked example — anti-patterns

Anti-patternEffect
local-exec provisioners for core resourcesHidden imperative steps
Mixing CLI changes with Terraform-managed resourcesDrift
Huge conditional logic in HCLHard to read — move to code generation
Table 13 — Common mistakes

6.10 Worked example — provisioners

GuidanceWhy
Avoid local-exec/remote-execNot idempotent, not in plan
Use user data, images, or configuration toolsProper mechanisms
Table 14 — Last resort only

6.11 Worked example — partial failure

ScenarioDeclarative recovery
Apply fails after 10 of 15 resourcesState records 10; re-apply creates remaining 5
Table 15 — Resumable by design

6.12 Worked example — Kubernetes as declarative example

ResourceBehaviour
Deployment replicas: 5Controller ensures 5 always
Table 16 — Continuous reconciliation

6.13 Worked example — Crossplane composition

ConceptUse
XRD + CompositionTeams request "Database" claims; controller creates RDS
Table 17 — Declarative self-service in Kubernetes

6.14 Worked example — declarative data sources

UseExample
data "aws_ami"Look up latest approved AMI
Table 18 — Reading, not managing

6.15 Worked example — computed values

CaseApproach
CIDR allocationcidrsubnet() functions or IPAM
Complex naminglocals
Table 19 — Declarative computation

6.16 Worked example — dynamic blocks

HCL
dynamic "ingress" {
  for_each = var.allowed_ports
  content {
    from_port = ingress.value
    to_port   = ingress.value
    protocol  = "tcp"
    cidr_blocks = [var.vpc_cidr]
  }
}

^Declarative repetition

6.17 Worked example — when CDK beats HCL

NeedReason
Heavy logic, strong typing, unit testsGeneral-purpose language
AWS-onlyCloudFormation backend fine
Table 20 — Team skills matter

6.18 Worked example — migration from scripts

StepDetail
Inventory resources created by scriptsTags/CloudTrail
Write HCL, importAdopt
Delete scriptsSingle path
Table 21 — Retire imperative provisioning

6.19 Worked example — interview follow-ups

Follow-upAnswer
"Is CDK declarative?"Authoring is imperative; output (CloudFormation) is declarative
"Is Ansible declarative?"Modules aim for idempotency, but playbooks are ordered steps
Table 22 — Nuances

6.20 Worked example — glue pipelines

StepType
terraform apply (network)Declarative
Run DB migrationImperative
argocd syncDeclarative (pull)
Table 23 — Pipelines orchestrate both

6.21 Worked example — error visibility

ModelWhere errors appear
Declarative pushPlan/apply output in CI
Pull controllersResource status/events
Table 24 — Monitor both

6.22 Worked example — performance

FactorEffect
Refresh of large statesSlower plans — split stacks
Imperative scriptsFast but unsafe
Table 25 — Speed vs safety

6.23 Worked example — checklist

Item✔
Declarative for infrastructure✔
Imperative only for migrations/ops/glue✔
No provisioners for core resources✔
Plans reviewed✔
Reconciliation model chosen per layer✔
Table 26 — Approach baseline

6.24 Worked example — reading a plan summary

Summary lineMeaning
Plan: 1 to add, 0 to change, 0 to destroyDeclarative diff
No changesConverged
Table 27 — Diff as communication

6.25 Worked example — CloudFormation change sets

StepDetail
create-change-setPreview
Review replacementsReplacement: True flags
execute-change-setApply
Table 28 — Declarative preview in CloudFormation

6.26 Worked example — drift in pull vs push

ModelDrift handling
PushDetected at next plan/drift job
PullCorrected continuously
Table 29 — Choose per risk

6.27 Worked example — team skills mapping

TeamPreferred authoring
Platform (Ops background)HCL
Application developersCDK (TypeScript/Java)
Table 30 — Both produce declarative deployments

6.28 Worked example — CDK in Java

JAVA
Bucket media = Bucket.Builder.create(this, "Media")
    .versioned(true)
    .encryption(BucketEncryption.KMS_MANAGED)
    .blockPublicAccess(BlockPublicAccess.BLOCK_ALL)
    .build();

^Java teams can author infrastructure in Java

6.29 Worked example — imperative bootstrap script (contained)

StepDetail
Create state bucket + lockOne-time script
Import into TerraformThen managed declaratively
Table 31 — Chicken-and-egg solved once

7Common Problems & Mistakes

MistakeFix
Imperative provisioningDeclarative tools
Provisioner abuseImages/user data
Out-of-band CLI editsPipeline-only
Logic-heavy HCLCode generation (CDK)
Table 32 — Mistakes and fixes

Most problems here come from imperative habits leaking into declarative tooling. Teams keep provisioning scripts alongside Terraform, so some resources have two owners and nobody knows which is authoritative. Engineers fix incidents with console clicks or CLI commands on Terraform-managed resources, creating drift that the next apply silently reverts or that blocks the plan. Provisioners are used to install software instead of baking images. HCL is stretched with nested conditionals and complex for expressions until nobody can review it; at that point, generating declarative output from a real language with CDK is clearer. Deletion by omission is the last trap: removing a block destroys the resource, so every destroy line in a plan deserves a second look.

8Performance & Production Considerations

ConsiderationPractice
Plan durationSmaller stacks
Controller load (pull)Rate limits
Partial failuresRe-apply/reconcile
Table 33 — Operating approaches

Declarative tools slow down as state grows, because every plan refreshes every resource through the provider APIs. A stack with thousands of resources can take many minutes to plan and hit API rate limits, so split state by layer and ownership, such as network, shared data and per-service stacks, and keep targeted applies for emergencies. Pull-based controllers have their own load: each reconciliation polls cloud APIs, so tune sync intervals and watch for throttling. Plan for partial failure as a normal event: declarative runs are resumable, so the response to a failed apply is to fix the cause and re-apply, not to repair resources by hand. Track plan duration and drift frequency as health signals for the infrastructure codebase.

9Architecture & Design Considerations

GuidelineDetail
Declarative coreDefault
Imperative at edgesContained
Choose push/pull per layerFit
Table 34 — Design guidance

10Interview Questions & Answers

Q1BasicConcept

What is declarative infrastructure?

Declarative infrastructure means writing down the desired end state, for example a VPC with three private subnets in three zones, and letting a tool work out how to get there. Tools such as Terraform, CloudFormation and Kubernetes compare the declaration with what actually exists, compute the difference, and create, change or delete only what is needed. The author models resources and their relationships rather than writing steps, so ordering comes from dependencies, re-running is safe, and removing a block from the code removes the resource. The tool's plan or change set becomes the review artefact. That predictability is why declarative tooling is the default for cloud infrastructure in most organisations.

Q2BasicConcept

What is imperative infrastructure?

Imperative infrastructure means writing the ordered steps that create or change resources: call create-vpc, then create-subnet for each zone, then attach route tables, usually in a shell script, SDK program or CLI sequence. It is flexible, because any logic can be expressed, but the author owns everything a declarative tool would handle: checking whether resources already exist, running steps in the right order, recovering from partial failure, avoiding duplicates on re-run and writing explicit delete steps. Without those guards, re-running a script after a timeout can create a second NAT gateway, as happened at ShopNow. Imperative approaches still fit migrations, bootstrap and operational actions, which are naturally sequences.

Q3IntermediateInternals

Why are declarative tools idempotent?

Because they act on the difference between desired and actual state, not on a list of instructions. On every run, Terraform reads the configuration, refreshes the real state of each resource from the provider APIs, compares the two and plans only the changes needed to converge. If everything already matches, the plan is empty and apply does nothing, so running it twice has the same effect as running it once. Kubernetes controllers do the same continuously in a reconciliation loop. Idempotency also makes partial failure recoverable: if an apply fails after ten of fifteen resources, state records those ten, and the next apply creates only the remaining five, without duplicates.

Q4IntermediateInternals

How does Terraform determine order?

Terraform builds a dependency graph from references between resources. When a subnet's vpc_id refers to aws_vpc.main.id, Terraform adds an edge meaning the VPC must exist before the subnet. It then walks the graph, creating independent resources in parallel, ten at a time by default, and destroying in reverse order. You never write the order yourself; it falls out of the data flow. When a dependency is real but not visible through attributes, such as an IAM policy that must exist before a service uses the role, you add an explicit depends_on. Overusing depends_on is a smell, because it hides why the order matters and serialises work that could run in parallel.

Q5Senior EngineerTrade-off

When is imperative appropriate?

Imperative is appropriate when the work is genuinely a sequence of actions rather than a state to maintain. Database schema migrations are ordered, one-time steps, best run with versioned tools such as Flyway or Liquibase. Bootstrap tasks, like creating the Terraform state bucket and the first role, are chicken-and-egg problems solved once by a small, documented script, after which the result is imported into declarative code. Operational actions, such as failover, restarts or scaling during an incident, are actions, not state, and belong in runbooks or SSM Automation. Glue between tools fits pipeline steps. Keep imperative work at the edges, versioned and reviewed, and never use it to create core infrastructure.

Q6Senior EngineerTricky

Is CDK declarative?

Partly, and the distinction is worth making. CDK authoring is imperative: you write TypeScript, Java or Python with loops, conditions, classes and functions. But the output is declarative. cdk synth runs your code to produce a CloudFormation template, and CloudFormation then performs a normal declarative deployment with change sets, dependency ordering, rollback and drift detection. Your program describes what the template should contain; it does not call AWS APIs to create resources step by step. That gives you general-purpose language features such as typing, unit tests and abstraction, which suit teams generating many similar resources, while keeping the safety of declarative deployment. Pulumi works in a similar way with its own engine.

Q7Technical ArchitectTrade-off

Push vs pull reconciliation?

Push reconciliation applies changes on demand, typically from a CI pipeline running terraform plan and apply. Its strengths are explicit plans, clear review and change points, and an audit trail for every apply; its weakness is that drift goes unnoticed until the next plan or a scheduled drift job. Pull reconciliation runs controllers that watch desired state and continuously converge reality towards it, as Argo CD, Crossplane and AWS Controllers for Kubernetes do. That gives self-healing and fast drift correction, but previews are harder and you must operate the controllers themselves. Many organisations combine them, as ShopNow does: push-based Terraform for AWS infrastructure and pull-based Argo CD for Kubernetes applications.

Q8Technical ArchitectTricky

Why avoid provisioners?

Terraform provisioners, local-exec and remote-exec, run arbitrary scripts during resource creation, which smuggles imperative steps into a declarative tool. Their effects do not appear in the plan, so reviewers cannot see what will happen. They are not idempotent and not tracked in state: if a provisioner fails, the resource is marked tainted and replaced on the next apply, and if it succeeds, Terraform never re-runs or reconciles it. They also create hidden dependencies on the machine running Terraform, such as installed tools and network access. Use proper mechanisms instead: baked images, user data or cloud-init, configuration management, or a separate pipeline step. HashiCorp itself describes provisioners as a last resort.

Q9Solution ArchitectDesign

How do you mix approaches safely?

Keep a declarative core and push imperative work to the edges, contained and orchestrated. All long-lived infrastructure is defined in Terraform, CloudFormation or Kubernetes manifests and changed only through reviewed plans. Imperative steps, such as database migrations, bootstrap scripts and operational actions, are versioned, idempotent where possible, and run by pipelines or SSM Automation rather than by people at a terminal. A pipeline sequences them, for example terraform apply, then a Flyway migration job, then an Argo CD sync. Ban out-of-band CLI changes to managed resources, because they create drift, and run drift detection regularly. PayGrid goes further, allowing no imperative changes to cardholder-data infrastructure except approved SSM runbooks.

Q10Solution ArchitectConcept

What's the review artefact for declarative changes?

The plan or change set. terraform plan, or a CloudFormation change set, shows exactly what will be added, changed, replaced or destroyed, computed against real state, so reviewers approve the effect of a change rather than guessing from a code diff. A summary such as 1 to add, 0 to change, 0 to destroy communicates intent, while any destroy or forced replacement deserves close attention, especially on stateful resources such as databases. In a pipeline, save the plan file, show it in the pull request, require approval, and apply exactly that saved plan so nothing changes between review and execution. Add prevent_destroy or policy-as-code checks on critical resources as a further safeguard.

Newsletter

Get new tutorials every week

New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.

Almost done! Check your inbox and click the confirmation link to complete your subscription.
Keep going in Book 9

Cloud, Containers, DevOps & Production Architecture

Volume I · Topic 1.1

What Is Cloud Computing?

Cloud Computing & Cloud Architecture Fundamentals

Volume I · Topic 1.2

Cloud Service Models

Cloud Computing & Cloud Architecture Fundamentals

Volume I · Topic 1.3

Cloud Deployment Models

Cloud Computing & Cloud Architecture Fundamentals

Volume II · Topic 1.1

Regions

AWS & Cloud Services

Volume II · Topic 1.2

Availability Zones

AWS & Cloud Services

Volume II · Topic 1.3

Edge Locations

AWS & Cloud Services

Volume III · Topic 1.1

Containers

Containers & Kubernetes

Volume III · Topic 1.2

Containers vs Virtual Machines

Containers & Kubernetes

Volume III · Topic 1.3

Container Lifecycle

Containers & Kubernetes

Volume IV · Topic 1.1

IaC Fundamentals

Infrastructure as Code & Platform Engineering

Volume IV · Topic 1.3

Immutable Infrastructure

Infrastructure as Code & Platform Engineering

Volume V · Topic 1.1

DevOps

CI/CD & DevOps Architecture

Volume V · Topic 1.2

Development + Operations

CI/CD & DevOps Architecture

Volume V · Topic 1.3

Continuous Integration

CI/CD & DevOps Architecture

Volume VI · Topic 1.1

Production Readiness Review

Production Architecture & Reliability Engineering

Volume VI · Topic 1.2

Operational Requirements

Production Architecture & Reliability Engineering

Volume VI · Topic 1.3

Capacity

Production Architecture & Reliability Engineering

Volume VII · Topic 1.1

Cloud-Native Principles

Cloud-Native Architecture & Multi-Region Systems

Volume VII · Topic 1.2

Twelve-Factor Applications

Cloud-Native Architecture & Multi-Region Systems

Volume VII · Topic 1.3

Immutable Infrastructure

Cloud-Native Architecture & Multi-Region Systems

Volume VIII · Topic 1.1

Architecture Drivers

Cloud Architecture Design, Cost & Migration

Volume VIII · Topic 1.2

Cloud Service Selection

Cloud Architecture Design, Cost & Migration

Volume VIII · Topic 1.3

Managed vs Self-Managed

Cloud Architecture Design, Cost & Migration