Functional Programming in JavaScript
JavaScript is not a pure functional language, but it has all the ingredients: first-class functions, closures, anonymous functions, and a standard library full of higher-order helpers. Functional programming in JS is more about a style — favour pure functions, avoid mutation, build behaviour by composing small pieces — than about following a strict paradigm.
Pure functions
A function is pure when it has two properties:
Its return value depends only on its arguments — no reading globals, no
Date.now(), noMath.random().It has no side effects — no writing to outer variables, no console, no network, no DOM.
// Pure: same input -> same output, no side effects.
const add = (a, b) => a + b;
// Impure: depends on hidden state.
let count = 0;
const inc = () => ++count;
// Impure: writes to the outside world.
const greet = name => console.log("hi " + name);Pure functions are easy to test (no setup), easy to memoise (the result only depends on the arguments) and easy to reason about. The cost is that the interesting parts of an app — IO, mutation, time — still need to happen somewhere. The trick is to keep that boundary thin.
Immutability — never mutate, always copy
Mutating shared data turns bugs into action-at-a-distance. The functional move is to return a new value instead.
Mutating vs replacing
// Mutating — risky in shared code.
function addTag(post, tag) {
post.tags.push(tag);
return post;
}
// Immutable — caller's original is untouched.
function addTagPure(post, tag) {
return { ...post, tags: [...post.tags, tag] };
}Tools like toSorted, toReversed and the spread operators in arrays/objects make immutable updates ergonomic without a library. For deeper trees, Immer lets you write code that looks mutating but produces a new immutable copy under the hood.
Higher-order functions
A function that takes a function, returns a function, or both. Array methods are the easiest example: map, filter, reduce, some, every all take a callback.
const orders = [
{ id: 1, total: 20, paid: true },
{ id: 2, total: 30, paid: false },
{ id: 3, total: 50, paid: true },
];
const paidRevenue = orders
.filter(o => o.paid)
.map(o => o.total)
.reduce((acc, n) => acc + n, 0);
console.log(paidRevenue); // 70Compare a hand-written loop: same result, more lines, more state to track. The chain reads "keep paid orders, take their totals, sum them" — almost prose.
Composition over inheritance
A small compose helper takes functions and runs them right-to-left. pipe is the same idea, left-to-right.
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const trim = s => s.trim();
const lower = s => s.toLowerCase();
const dashes = s => s.replace(/\s+/g, "-");
const slug = pipe(trim, lower, dashes);
console.log(slug(" Hello World ")); // "hello-world"Each piece does one thing. Re-use them anywhere, swap one out, snap in a new step — without rewriting the others.
Currying and partial application
A curried function takes its arguments one at a time. Partial application is fixing some arguments now and the rest later.
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(7)); // 14
console.log(triple(7)); // 21
// With `bind`:
const greet = (greeting, name) => greeting + ", " + name;
const hi = greet.bind(null, "Hi");
console.log(hi("Ada")); // "Hi, Ada"Partial application turns generic helpers into specialised ones with no new code — exactly what closures are for.
Avoiding shared state
Code that mutates module-level variables behaves differently depending on order of calls. Code that takes everything it needs as arguments behaves the same every time.
Shared state — hard to test
let user = null;
function login(name) { user = { name }; }
function greet() { return user ? "hi " + user.name : "hi guest"; }Pure version — pass state in
const login = name => ({ name });
const greet = user => user ? "hi " + user.name : "hi guest";
greet(login("Ada")); // "hi Ada"Where FP fits in real code
Data transformations — pipelines of
map/filter/reduceare clearer than loops with mutation.React/Redux — components and reducers are pure functions. Hooks are higher-order.
Validation and parsing — small composable rules combined into bigger ones.
Concurrency — pure code is safe to run anywhere, including a Web Worker.