Functional programming in JS is a style built around pure functions, immutability, and composition. React hooks, Redux reducers, and array methods are all functional patterns. Knowing why the style exists helps you answer follow-up questions confidently.
Pure functions
A function is pure if:
- Given the same inputs, it always returns the same output (deterministic).
- It produces no side effects โ no mutations to external state, no network calls, no I/O.
// Pure โ same input โ same output, nothing external mutated
const add = (a, b) => a + b;
const double = arr => arr.map(x => x * 2); // returns new array
// Impure โ reads/mutates external state
let count = 0;
const increment = () => count++; // mutates outer variable
const getUser = (id) => fetch(`/users/${id}`); // side effect (network)
Pure functions are easy to test (no mocks needed), composable, and safe to memoize (same input โ same output, safe to cache).
Immutability
Treat data as read-only โ return new structures instead of mutating:
console.log. Async logs (setTimeout/Promise) appear in real execution order.The spread operator (...) does a shallow copy โ nested objects are still shared references. For deep updates, spread each nested level, use structuredClone, or use a library like Immer.
Higher-order functions โ map, filter, reduce
A higher-order function takes a function as argument or returns one. The most important built-ins:
console.log. Async logs (setTimeout/Promise) appear in real execution order.Implement reduce from scratch โ a common interview ask:
Array.prototype.myReduce = function (fn, initialValue) {
let acc = initialValue !== undefined ? initialValue : this[0];
const start = initialValue !== undefined ? 0 : 1;
for (let i = start; i < this.length; i++) {
acc = fn(acc, this[i], i, this);
}
return acc;
};
Currying
Currying transforms a function of N arguments into N chained functions of 1 argument each. The key insight: partial application โ you can call with fewer args now and the rest later.
console.log. Async logs (setTimeout/Promise) appear in real execution order.Implement a generic curry():
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args); // enough args โ call it
}
return function (...more) {
return curried.apply(this, args.concat(more)); // collect more
};
};
}
const curriedAdd = curry((a, b, c) => a + b + c);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6
Function composition
Composition chains functions: the output of one becomes the input of the next. compose(f, g)(x) = f(g(x)).
// Right-to-left (compose) โ classic math order
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
// Left-to-right (pipe) โ more readable for most JS devs
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const add1 = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const transform = pipe(add1, double, square); // (x+1)*2, then squared
console.log(transform(3)); // (3+1)*2 = 8, 8ยฒ = 64
Composition shines when you build complex transformations from small, testable, reusable pieces โ every step is pure, so each is trivially testable in isolation.
pipe does this left-to-right. These patterns are why React hooks, Redux reducers, and array methods all feel the same: theyโre all just pure function composition.โ