Declarative vs imperative — describing what, not how
map/filter/reduce, SQL, and CSS all share something a raw for loop doesn't: they describe the result you want, not the step-by-step mechanics of getting there. That distinction has a name, and it's the thread connecting most of functional programming's individual techniques.
Beginner
2 min read
Two ways to ask for the same result
// Imperative: step-by-step instructions for HOW to get the resultconst evens = [];for (let i = 0; i < numbers.length; i++) { if (numbers[i] % 2 === 0) { evens.push(numbers[i]); }}// Declarative: describes WHAT result is wanted, not the mechanicsconst evens2 = numbers.filter(n => n % 2 === 0);
Both produce the identical result. The imperative version spells out every
mechanical step — initialize an index, check a bound, increment, check a
condition, mutate an array. The declarative version states the goal
("the even numbers") and hands the mechanics to filter entirely — the
reader doesn't have to trace through index bookkeeping to know what the
code is for.
This distinction shows up far beyond functional programming
SQL — SELECT name FROM users WHERE age > 18 describes what rows
are wanted; the database engine decides how to actually fetch them
(which index to use, what order to scan). You never write the loop.
CSS — .card { display: flex; justify-content: center; } describes
the desired layout; the browser's rendering engine works out the actual
positioning math to achieve it.
React/declarative UI — a component describes what the UI should look
like for a given state; the framework figures out which specific DOM
updates are needed to get there, rather than the developer manually
calling appendChild/removeChild.
None of these are "functional programming" by name, but they all share the
same underlying idea map/filter/reduce demonstrate at the smallest
scale: say what you want, not the mechanical steps to produce it.
Why declarative code tends to be easier to trust
Imperative code's correctness depends on getting every mechanical detail
right — the loop bound, the increment, the mutation order — any of which can
individually be wrong in a way that's easy to miss on a quick read.
Declarative code shifts that correctness burden onto a well-tested,
already-verified building block (filter, the SQL engine, the rendering
engine) — trusting numbers.filter(...) to correctly filter is a much
smaller leap than manually re-verifying a hand-written loop's index
arithmetic every single time you read it.
Why this matters day to day
"Could this be written more declaratively?" is a genuinely useful question
to ask when reviewing or writing a loop — not because imperative code is
always wrong, but because a declarative version is very often shorter,
easier to verify at a glance, and less prone to the small mechanical bugs
(off-by-one errors, forgotten mutations) that hand-written loops are
specifically prone to.