Skip to main content

Implementing compose

compose() is a combinator function that merges several functions into one: compose(f, g, h)(x) is equivalent to f(g(h(x))) (it executes right to left).


Minimal implementation (ES6)

javascript
// Universal version: the last handler can accept many arguments const compose = (...fns) => (...args) => fns.reduceRight( (acc, fn) => [fn(...acc)], args )[0]; // Example const trim = s => s.trim(); const toInt = s => parseInt(s, 10); const inc = n => n + 1; const parseAndInc = compose(inc, toInt, trim); parseAndInc(' 41 '); // 42

A variant for unary functions (a bit faster and simpler)

javascript
const composeUnary = (...fns) => x => fns.reduceRight((v, fn) => fn(v), x);

Promise-aware (asynchronous compose)

Works with both sync and async functions.

javascript
const composeAsync = (...fns) => (...args) => fns .slice(0, -1) .reduceRight( (p, fn) => p.then(res => fn(res)), Promise.resolve(fns[fns.length - 1](...args)) ); // Example const fetchUser = async id => ({ id, name: 'Tim' }); const getName = u => u.name; const shout = s => s.toUpperCase(); const getUserNameLoud = composeAsync(shout, getName, fetchUser); getUserNameLoud(7).then(console.log); // 'TIM'

A type-safe version for TypeScript (practical overloads)

(Covers up to 4 functions; can be extended the same way.)

javascript
type Unary<A, R> = (a: A) => R; export function compose<A, R>(f1: Unary<A, R>): Unary<A, R>; export function compose<A, B, R>( f1: Unary<B, R>, f2: Unary<A, B> ): Unary<A, R>; export function compose<A, B, C, R>( f1: Unary<C, R>, f2: Unary<B, C>, f3: Unary<A, B> ): Unary<A, R>; export function compose<A, B, C, D, R>( f1: Unary<D, R>, f2: Unary<C, D>, f3: Unary<B, C>, f4: Unary<A, B> ): Unary<A, R>; export function compose(...fns: Function[]) { return (x: unknown) => fns.reduceRight((v, f) => f(v), x); }

You often also need pipe() (left to right)

javascript
const pipe = (...fns) => (...args) => fns.reduce((acc, fn) => [fn(...acc)], args)[0]; const parseAndInc2 = pipe(trim, toInt, inc); parseAndInc2(' 41 '); // 42

Useful notes

  • If you're composing object methods, don't forget about this: bind them with fn.bind(obj).
  • Try to keep functions pure and unary - composition will be simpler and more predictable.
  • For production it's convenient to have both variants: compose (R→L) and pipe (L→R), plus composeAsync/pipeAsync.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.