You've chained data through a few transformations — trim a string, lowercase it, then split it. Writing split(lower(trim(input))) reads inside-out, and it gets worse with every step. Function composition gives you a single reusable function built from the pieces, applied right-to-left so the picture matches the math: compose(f, g, h)(x) === f(g(h(x))).
Implement a compose function that takes any number of unary functions and returns a new function. Calling that new function with an input runs the rightmost function first, feeds its result into the next, and so on, returning the final value. See MDN on function composition for background.
function compose(...fns) {
// returns a new function that, when called with `x`,
// applies the rightmost fn to `x` and pipes results leftward
}
const addOne = (n) => n + 1;
const double = (n) => n * 2;
const f = compose(addOne, double);
f(3); // 7 — double(3) = 6, then addOne(6) = 7
const shout = compose(
(s) => s + '!',
(s) => s.toUpperCase(),
(s) => s.trim(),
);
shout(' hello '); // 'HELLO!'
compose()(x) must return x unchanged.compose(f)(x) must return f(x).fns array.You'll build a function that glues other functions together into a single pipeline, applied right-to-left so the call site reads the way you'd write the math.
You have three small functions: trim, toUpperCase, and addBang. To shout a string you'd write addBang(toUpperCase(trim(input))) — nested, read inside-out. compose lets you write compose(addBang, toUpperCase, trim) once, save it as shout, and call shout(input) anywhere.
Think of compose as a conveyor belt where the rightmost function sits at the entry point. Your input drops in on the right, each station transforms it, and the final value rolls off the left. Your argument list is the reading order; the run order is the reverse.
A natural first try uses a plain for loop in the order the arguments arrive:
function composeBroken(...fns) {
return function (x) {
let result = x;
for (const fn of fns) result = fn(result);
return result;
};
}
This runs the functions left-to-right — exactly backwards. composeBroken(addOne, double)(3) gives 8 (addOne first, then double), but the spec says compose should give 7 (double first, then addOne). The shape is right; the direction is wrong.
Walk the array from right to left, threading the running value through each function:
function compose(...fns) {
// Named function is optional; it shows up in stack traces as `composed`.
return function composed(x) {
// reduceRight visits fns from last to first;
// `acc` is the value piped in, `fn` is the next station leftward.
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
module.exports = { compose };
The critical move: reduceRight walks the array backwards, so the rightmost function (at index fns.length - 1) runs first on the seed value x. Using reduce instead would process left-to-right and flip the order — that's the trap in the gotcha below.
reduceRight also handles the empty case automatically: with zero functions, it returns the seed unchanged, so compose()(x) === x.
Take compose(addOne, double)(3) where addOne = n => n + 1 and double = n => n * 2. The fns array is [addOne, double]. reduceRight walks from index 1 down to 0:
acc = 3 (the seed, the input x).double. acc = double(3) = 6.addOne. acc = addOne(6) = 7.7.The rightmost function (double) ran first; its output fed leftward into addOne. That's the whole pipeline.
reduce instead of reduceRight — runs left-to-right and reverses the intended order. Fix: reduceRight, or fns.slice().reverse().reduce(...).reduceRight — without an initial value, the last function in fns is used as the seed instead of being called, and compose()(x) throws on an empty array. Always pass x as the second argument.fns[0] directly works for compose(f)(x) but skips the uniform wrapper. reduceRight handles single-element arrays without a branch.fns — fns.reverse() flips the array the caller passed in. reduceRight doesn't mutate; if you need to reverse, slice() first.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
You've chained data through a few transformations — trim a string, lowercase it, then split it. Writing split(lower(trim(input))) reads inside-out, and it gets worse with every step. Function composition gives you a single reusable function built from the pieces, applied right-to-left so the picture matches the math: compose(f, g, h)(x) === f(g(h(x))).
Implement a compose function that takes any number of unary functions and returns a new function. Calling that new function with an input runs the rightmost function first, feeds its result into the next, and so on, returning the final value. See MDN on function composition for background.
function compose(...fns) {
// returns a new function that, when called with `x`,
// applies the rightmost fn to `x` and pipes results leftward
}
const addOne = (n) => n + 1;
const double = (n) => n * 2;
const f = compose(addOne, double);
f(3); // 7 — double(3) = 6, then addOne(6) = 7
const shout = compose(
(s) => s + '!',
(s) => s.toUpperCase(),
(s) => s.trim(),
);
shout(' hello '); // 'HELLO!'
compose()(x) must return x unchanged.compose(f)(x) must return f(x).fns array.You'll build a function that glues other functions together into a single pipeline, applied right-to-left so the call site reads the way you'd write the math.
You have three small functions: trim, toUpperCase, and addBang. To shout a string you'd write addBang(toUpperCase(trim(input))) — nested, read inside-out. compose lets you write compose(addBang, toUpperCase, trim) once, save it as shout, and call shout(input) anywhere.
Think of compose as a conveyor belt where the rightmost function sits at the entry point. Your input drops in on the right, each station transforms it, and the final value rolls off the left. Your argument list is the reading order; the run order is the reverse.
A natural first try uses a plain for loop in the order the arguments arrive:
function composeBroken(...fns) {
return function (x) {
let result = x;
for (const fn of fns) result = fn(result);
return result;
};
}
This runs the functions left-to-right — exactly backwards. composeBroken(addOne, double)(3) gives 8 (addOne first, then double), but the spec says compose should give 7 (double first, then addOne). The shape is right; the direction is wrong.
Walk the array from right to left, threading the running value through each function:
function compose(...fns) {
// Named function is optional; it shows up in stack traces as `composed`.
return function composed(x) {
// reduceRight visits fns from last to first;
// `acc` is the value piped in, `fn` is the next station leftward.
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
module.exports = { compose };
The critical move: reduceRight walks the array backwards, so the rightmost function (at index fns.length - 1) runs first on the seed value x. Using reduce instead would process left-to-right and flip the order — that's the trap in the gotcha below.
reduceRight also handles the empty case automatically: with zero functions, it returns the seed unchanged, so compose()(x) === x.
Take compose(addOne, double)(3) where addOne = n => n + 1 and double = n => n * 2. The fns array is [addOne, double]. reduceRight walks from index 1 down to 0:
acc = 3 (the seed, the input x).double. acc = double(3) = 6.addOne. acc = addOne(6) = 7.7.The rightmost function (double) ran first; its output fed leftward into addOne. That's the whole pipeline.
reduce instead of reduceRight — runs left-to-right and reverses the intended order. Fix: reduceRight, or fns.slice().reverse().reduce(...).reduceRight — without an initial value, the last function in fns is used as the seed instead of being called, and compose()(x) throws on an empty array. Always pass x as the second argument.fns[0] directly works for compose(f)(x) but skips the uniform wrapper. reduceRight handles single-element arrays without a branch.fns — fns.reverse() flips the array the caller passed in. reduceRight doesn't mutate; if you need to reverse, slice() first.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.