Imperative vs Declarative in JavaScript/TypeScript
A fun and practical guide to understanding when to use imperative vs declarative approaches in JS/TS, with lots of real-world examples. - 6 Sept 2025
4 min read

๐ณ Imperative vs Declarative in JavaScript/TypeScript
When you tell your friend how to get to your house, you have two choices:
- Imperative style: โGo straight, take the second left, then right after the tea shop, then climb the stairsโฆโ
- Declarative style: โJust follow Google Maps.โ
Both get the job done, but one micromanages every step, while the other simply declares the goal and lets the system figure it out.
Thatโs the essence of imperative vs declarative programming. And JavaScript/TypeScript let you dance between both worlds depending on the problem.
Letโs explore them with code, cooking metaphors, and real-world use cases.
๐ฆ What Do These Words Even Mean?
-
Imperative = HOW You explain the steps. The computer follows instructions line by line.
-
Declarative = WHAT You explain the intent. The computer decides the steps internally.
Think of it like cooking:
- Imperative โ โChop onions, heat oil, fry until golden.โ
- Declarative โ โI want onion curry.โ
Tip: Keep this analogy in mind when switching styles: Imperative = recipe instructions. Declarative = placing an order at a restaurant.
๐ First Taste: Arrays
Square numbers imperatively
const numbers = [1, 2, 3, 4];
const squares: number[] = [];
for (let i = 0; i < numbers.length; i++) {
squares.push(numbers[i] * numbers[i]);
}
Square numbers declaratively
const numbers = [1, 2, 3, 4];
const squares = numbers.map(n => n * n);
๐ Declarative code reads like the intention, not the mechanics.
๐ Everyday Use Cases
1. Filtering & Mapping Data
Imperative
const evens: number[] = [];
for (let n of [1, 2, 3, 4, 5]) {
if (n % 2 === 0) evens.push(n * 2);
}
Declarative
const evens = [1, 2, 3, 4, 5]
.filter(n => n % 2 === 0)
.map(n => n * 2);
Watch out! Chained methods (filter โ map โ reduce
) are beautiful,
but chaining too much may hide performance issues.
2. Async Workflows
Declarative (promise chain)
fetch("/api/data")
.then(r => r.json())
.then(items => items.filter((x: any) => x.active))
.then(active => fetch("/api/save", {
method: "POST", body: JSON.stringify(active)
}))
.catch(console.error);
Imperative (async/await)
try {
const r = await fetch("/api/data");
const items = await r.json();
const active = items.filter((x: any) => x.active);
await fetch("/api/save", { method: "POST", body: JSON.stringify(active) });
} catch (err) {
console.error(err);
}
Rule of thumb:
Use async/await
(imperative) for retries, cancellation, and error handling.
Use promise chains (declarative) for lightweight data flows.
3. DOM & UI Updates
Imperative DOM
const button = document.createElement("button");
button.textContent = "Click me";
button.addEventListener("click", () => alert("Clicked!"));
document.body.appendChild(button);
Declarative React
function App() {
return <button onClick={() => alert("Clicked!")}>Click me</button>;
}
UI frameworks like React, Vue, and Svelte are declarative by design.
They let you focus on what UI should look like instead of how to build it.
4. Validation
Imperative
function validateEmail(email: string) {
if (!email.includes("@")) return false;
return true;
}
Declarative
import { z } from "zod";
const schema = z.object({ email: z.string().email() });
schema.parse({ email: "me@example.com" });
Pro Tip: Declarative schemas (like Zod, Yup) make validation scalable.
Instead of scattering rules everywhere, you define them once and reuse.
๐ฎ Fun Extra Examples
Timers (imperative is better)
let counter = 0;
const id = setInterval(() => {
console.log("Tick", ++counter);
if (counter >= 5) clearInterval(id);
}, 1000);
Animations (declarative is better)
<div class="box"></div>
<style>
.box { transition: transform 0.5s; }
.box:hover { transform: scale(1.2); }
</style>
Game Loop (imperative is better)
let x = 0;
function loop() {
x += 1;
draw(x);
requestAnimationFrame(loop);
}
loop();
Data Queries (declarative is better)
const sales = orders
.filter(o => o.status === "paid")
.map(o => o.amount)
.reduce((a, b) => a + b, 0);
๐ Pros & Cons
Imperative ๐ณ | Declarative ๐ | |
---|---|---|
Readability | Verbose, low-level | Clean, high-level |
Control | Full control over details | Abstracted away |
Performance | Tunable & optimized | Usually โgood enoughโ |
Maintainable | Gets messy at scale | Easier to reason about |
Learning | Intuitive for beginners | Requires trust in abstractions |
๐ฏ Rules of Thumb
-
Choose declarative when:
- Code should express intent clearly.
- Youโre in frameworks like React, Vue, RxJS.
- Business rules/configs need to be changed easily.
-
Choose imperative when:
- You need fine-grained control (timers, resources).
- Performance-critical loops matter.
- Abstractions donโt fit edge cases.
Remember: Imperative = manual driving. Declarative = autopilot with GPS.
Both matter โ the art is knowing when to switch.
๐ Key Takeaway
Think of imperative code as manual driving mode and declarative code as autopilot with GPS.
Both are essential. One gives you control; the other gives you clarity.
The art of being a great developer is knowing when to switch gears.
Next time you write code, ask yourself:
Am I giving step-by-step driving directions, or just telling the system the destination?