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 '); // 42A 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 '); // 42Useful notes
- If you're composing object methods, don't forget about
this: bind them withfn.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) andpipe(L→R), pluscomposeAsync/pipeAsync.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.