Declarative vs Imperative
Describing what you want versus specifying how to achieve it step by step.
Overview
Imperative programming describes how to achieve a result: explicit step-by-step instructions that mutate state. Declarative programming describes what the result should be, leaving the how to the runtime or framework. SQL, HTML, CSS, and React's JSX are declarative; C, assembly, and most loop-based code are imperative. Most real programs mix both styles.
Origin
The distinction is as old as programming itself: FORTRAN (1957) was imperative; LISP (1958) introduced declarative list operations. SQL (1974, IBM) popularised declarative data querying. The web brought HTML (declarative layout) alongside JavaScript (imperative logic). React (2013) re-popularised declarative UI after years of jQuery-driven DOM mutation.
Examples
Imperative vs declarative array transformation
const orders = [
{ id: 1, status: 'shipped', total: 120 },
{ id: 2, status: 'pending', total: 80 },
{ id: 3, status: 'shipped', total: 200 },
];
// Imperative: tells the machine exactly what to do
const imperativeResult: number[] = [];
for (let i = 0; i < orders.length; i++) {
if (orders[i].status === 'shipped') {
imperativeResult.push(orders[i].total);
}
}
// Declarative: expresses the desired shape of the result
const declarativeResult = orders
.filter(o => o.status === 'shipped')
.map(o => o.total);
console.log(declarativeResult); // [120, 200]The declarative version is shorter, but the more important benefit is that filter and map communicate intent: we want shipped totals. The imperative version requires reading the loop body to infer that.
Declarative DOM with React vs imperative jQuery-style
// Imperative: manually manage DOM mutations
function imperativeToggle(buttonId: string, panelId: string) {
const btn = document.getElementById(buttonId)!;
const panel = document.getElementById(panelId)!;
btn.addEventListener('click', () => {
const isOpen = panel.style.display !== 'none';
panel.style.display = isOpen ? 'none' : 'block';
btn.textContent = isOpen ? 'Show' : 'Hide';
});
}
// Declarative: describe what the UI should look like given state
import { useState } from 'react';
function Toggle() {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(prev => !prev)}>
{open ? 'Hide' : 'Show'}
</button>
{open && <div className="panel">Content</div>}
</div>
);
}React reconciles the virtual DOM against the actual DOM. The developer declares the target state; React determines what mutations are needed. This inversion eliminates a class of bugs where state and DOM drift apart.
Use Cases
- 01Database querying via SQL where the query planner chooses indexes and join order better than hand-written loops
- 02Infrastructure as code (Terraform, Pulumi) where the desired cloud state is declared and the tool computes the diff and applies it
- 03CSS where the browser layout engine handles box model calculations the developer does not want to perform manually
- 04React and SwiftUI UIs where components describe target state and the framework minimises DOM/view tree mutations
When Not to Use
- //Performance-critical algorithms where the runtime's chosen strategy is suboptimal; sometimes manual loop control is necessary
- //Debugging complex declarative pipelines when the abstraction hides where an error originates; imperative code gives a clearer call stack
- //Highly stateful workflows (game loops, hardware drivers) where step-by-step mutation is the natural model and declarative abstractions add friction
Technical Notes
- SQL is declarative in the query language but imperative in stored procedures; the boundary between them is explicit and meaningful
- React's reconciler (Fiber, rewritten in 2017) uses a diffing algorithm to translate declarative component trees into minimal imperative DOM operations, achieving O(n) diffing by constraining the key heuristic
- Datalog (Clojure's Datomic query language) is a declarative query language derived from Prolog; Datomic uses it to query an immutable, append-only database
- Pure declarative systems lose expressive power for certain problems; most mature declarative tools (GraphQL directives, Terraform provisioners) include escape hatches for imperative logic
More in Types of Programming