Programming paradigms in one paragraph
A paradigm is a style of organising programs. JavaScript happily supports four:
- Imperative — describe the steps the computer takes (
forloops, mutable state). - Procedural — group steps into named procedures (functions called for their side effects).
- Object-oriented — bundle data with the methods that operate on it.
- Functional — model computation as the evaluation of pure functions, prefer immutable data, and compose small functions into larger ones.
JavaScript is sometimes called multi-paradigm because nothing in the language forces a single style. But its first-class functions, closures, and rich array methods make the functional style especially natural — and increasingly popular in front-end frameworks (React, Solid) and back-end ecosystems (RxJS, fp-ts).
If you have not yet read Functions in depth, start there — closures and higher-order functions are the engine of everything below.
The core ideas
1. Pure functions
A pure function satisfies two rules:
- Same input → same output. Always.
- No side effects: no I/O, no global mutation, no time, no randomness.
1// Pure
2const add = (a, b) => a + b;
3const upper = (s) => s.toUpperCase();
4const taxedTotal = (items, rate) =>
5 items.reduce((s, it) => s + it.price, 0) * (1 + rate);
6
7// Impure — reads from outer state and writes to it
8let counter = 0;
9function tick() { counter++; return counter; }
Pure functions are cache-able, parallelisable, and trivially testable. They are the chunks you should split most logic into.
2. Immutability
Don’t change data in place — derive a new value. Modern JavaScript makes this almost painless:
1// Add a tag without mutating
2const addTag = (post, tag) => ({ ...post, tags: [...post.tags, tag] });
3
4// Update by id
5const setStatus = (orders, id, status) =>
6 orders.map((o) => (o.id === id ? { ...o, status } : o));
The new ES2023 array siblings (toSorted, toReversed, toSpliced, with) reinforce the pattern.
Note
Immutability is not the same as “everything is
const”.constonly protects the binding; the object it points to can still be mutated. Use the spread/clone patterns above for true immutability.
3. First-class and higher-order functions
A first-class function can be stored, passed, and returned like any other value. A higher-order function takes or returns functions. The combination is what makes functional style fluent in JavaScript:
1const orders = await fetchOrders();
2
3const totalPaid = orders
4 .filter((o) => o.paid) // higher-order: accepts a predicate
5 .map((o) => o.total) // higher-order: accepts a transformer
6 .reduce((a, b) => a + b, 0); // higher-order: accepts a reducer
4. Composition
Build large operations by combining small ones:
1const pipe = (...fns) => (input) => fns.reduce((acc, fn) => fn(acc), input);
2
3const trim = (s) => s.trim();
4const lower = (s) => s.toLowerCase();
5const dasher = (s) => s.replace(/\s+/g, "-");
6const slugify = pipe(trim, lower, dasher);
7
8slugify(" Hello World "); // "hello-world"
Each stage is independently testable, and the pipeline reads top-to-bottom.
5. Currying and partial application
Currying transforms f(a, b, c) into f(a)(b)(c). Partial application pre-supplies some arguments. Both are useful for adapting a generic function to a specific call site without writing a wrapper:
1const map = (fn) => (xs) => xs.map(fn);
2
3const doubleAll = map((x) => x * 2);
4const upperAll = map(upper);
5
6doubleAll([1, 2, 3]); // [2, 4, 6]
7upperAll(["a", "b"]); // ["A", "B"]
The standard-library toolkit
Array.prototype alone covers most functional needs:
| Operation | Method |
|---|---|
| Transform each | map(fn) |
| Keep matches | filter(fn) |
| Reduce to one | reduce(fn, init) |
| Flatten | flat(depth), flatMap(fn) |
| Quantifiers | some(fn), every(fn) |
| First match | find(fn), findIndex(fn) |
| Pair / index | entries() |
For grouping, ES2024 added Object.groupBy and Map.groupBy — see the modern object features post.
For typed-pipeline operations over streams (asynchronous, infinite, back-pressured), reach for RxJS or the new TC39 Iterator-helpers proposal (already stage 4 — coming to a runtime near you).
A worked example — pipeline over orders
1const orders = [
2 { id: 1, customer: "Ali", total: 40, paid: true, region: "EU" },
3 { id: 2, customer: "Omar", total: 12, paid: false, region: "EU" },
4 { id: 3, customer: "Ali", total: 95, paid: true, region: "US" },
5 { id: 4, customer: "Zaid", total: 60, paid: true, region: "US" },
6];
7
8// Revenue per region, considering only paid orders
9const revenueByRegion = orders
10 .filter((o) => o.paid)
11 .reduce((acc, o) => {
12 acc[o.region] = (acc[o.region] ?? 0) + o.total;
13 return acc;
14 }, {});
15
16// → { EU: 40, US: 155 }
No mutation of orders. Each step is a pure transformation of the previous value. The intent reads in plain English.
Is JavaScript a functional language?
By academic definition — no. JavaScript allows side effects, makes immutability optional, and has no built-in algebraic data types or pattern matching (the pattern-matching proposal is in stage 1).
By practical use — increasingly yes. Modern code lives on map/filter/reduce, prefers immutable updates, and frameworks like React popularised the “UI as a pure function of state” model.
The honest answer: JavaScript supports the functional style well enough that you should reach for it by default, while keeping the pragmatic escape hatches when they are clearer.
When to be functional, when to be pragmatic
| Choose functional when… | Choose imperative when… |
|---|---|
| Transforming data, especially in pipelines. | Performance matters and the imperative version is much faster. |
| Code will run in parallel or in a UI re-render. | The “state machine” is the natural model (parsers, animations). |
| The function will be widely re-used. | The code is a one-off script. |
| You need to reason about correctness or write proofs. | You are interacting with hardware, sockets, or DOM. |
Functional libraries worth knowing
- Ramda — point-free, auto-curried; if you love function composition, this is its purest expression in JS.
- lodash/fp — immutable, curried versions of Lodash’s API.
- fp-ts / Effect — TypeScript-first; bring algebraic data types (Option, Either, Task) and effect systems.
- RxJS — reactive streams; functional pipelines over time.
- Immer — mutable-looking syntax that produces immutable updates, popular with React/Redux.
Summary
- A pure function is referentially transparent — same input, same output, no side effects.
- Prefer derivation over mutation; modern array and object syntax makes it cheap.
- Compose small functions into pipelines (
pipe,flow). - Currying and partial application turn generic helpers into call-site-specific tools.
- JavaScript is not a strict functional language, but the style is increasingly the default in modern codebases.
Next: JavaScript Modules — how to break code into reusable units across files and packages.










