Functional JS: Currying, Composition & Pure Functions

Pure functions, immutability, currying, partial application, and function composition โ€” the functional toolkit asked at mid-senior interviews.

deep medium โฑ 22 min functionalcurryingcompositionpure-functionsimmutabilityhigher-order
Mastery:
Why interviewers ask this
Functional patterns are deeply embedded in modern JS/TS โ€” React itself is functional. Interviewers probe whether you understand why these patterns exist, not just how to use them.

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:

  1. Given the same inputs, it always returns the same output (deterministic).
  2. 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:

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

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:

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

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.

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

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.

Say it out loud
โ€œA pure function always returns the same output for the same input and has no side effects โ€” that makes it deterministic, testable, and safe to memoize. Immutability means returning new structures instead of mutating. Currying transforms a multi-argument function into chained single-argument functions, enabling partial application. Function composition chains functions so output flows as input โ€” 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.โ€

Likely follow-up questions
  • What makes a function pure?
  • Implement curry() from scratch.
  • What is function composition, and why does it matter?
  • Difference between map, filter, and reduce โ€” implement reduce.
  • What is a higher-order function?

References