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

views

The logo of Imperative vs Declarative

๐Ÿณ 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 ๐Ÿ•
ReadabilityVerbose, low-levelClean, high-level
ControlFull control over detailsAbstracted away
PerformanceTunable & optimizedUsually โ€œgood enoughโ€
MaintainableGets messy at scaleEasier to reason about
LearningIntuitive for beginnersRequires 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?