Function Composition
The Pipeline
LinkedIn Hook
replaceSpaces(toLowerCase(trim(" Hello World "))).Read that again. Three function calls. You read it inside-out. Your brain parses
trimfirst, thentoLowerCase, thenreplaceSpaces— but the code is written in the opposite order.Function composition fixes that. With
pipe, you write the steps in the order they run:pipe(trim, toLowerCase, replaceSpaces). Input flows left-to-right through the functions like data through a factory assembly line. Raw input goes in, each station transforms it, a finished value comes out.
composeis the same idea but right-to-left — matching the mathematicalf(g(h(x)))notation.In this lesson you'll implement both
pipeandcomposein one line each, build a text-cleaning pipeline, a data-transformation pipeline, and learn point-free style — writing functions without ever naming the data they operate on.Read the full lesson -> [link]
#JavaScript #InterviewPrep #FunctionComposition #Pipe #Compose #FunctionalProgramming #CodingInterview
What You'll Learn
- What function composition is and why
pipe(f, g)(x)equalsg(f(x)) - The difference between
pipe(left-to-right) andcompose(right-to-left) - Point-free style and how composition makes it possible
The Assembly Line
Function composition is combining two or more functions to produce a new function. The output of one function becomes the input of the next. If you have functions f and g, composing them gives you f(g(x)).
Think of it like a factory assembly line, but instead of adding parts, each station transforms the product. Raw metal goes in, Station 1 cuts it, Station 2 bends it, Station 3 paints it. Each station is a pure function. The assembly line is composition.
Basic Composition (Manual)
const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const replaceSpaces = str => str.replace(/\s+/g, "-");
// Manual composition — read inside-out
const slug = replaceSpaces(toLowerCase(trim(" Hello World ")));
console.log(slug); // "hello-world"
// Problem: deeply nested calls are hard to read
// replaceSpaces(toLowerCase(trim(removeSpecialChars(validate(input)))))
Implement compose (Right-to-Left)
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const replaceSpaces = str => str.replace(/\s+/g, "-");
// compose reads right-to-left: replaceSpaces(toLowerCase(trim(x)))
const slugify = compose(replaceSpaces, toLowerCase, trim);
console.log(slugify(" Hello World ")); // "hello-world"
console.log(slugify(" JavaScript IS Fun ")); // "javascript-is-fun"
Implement pipe (Left-to-Right)
function pipe(...fns) {
return function(x) {
return fns.reduce((acc, fn) => fn(acc), x);
};
}
// pipe reads left-to-right: trim -> toLowerCase -> replaceSpaces
const slugify = pipe(
str => str.trim(),
str => str.toLowerCase(),
str => str.replace(/\s+/g, "-")
);
console.log(slugify(" Hello World ")); // "hello-world"
console.log(slugify(" JavaScript IS Fun ")); // "javascript-is-fun"
compose vs pipe
const add1 = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
// compose: right-to-left -> square(double(add1(3)))
const composed = compose(square, double, add1);
console.log(composed(3)); // square(double(4)) = square(8) = 64
// pipe: left-to-right -> add1 -> double -> square on 3
const piped = pipe(add1, double, square);
console.log(piped(3)); // add1(3)=4 -> double(4)=8 -> square(8)=64
// Same result, same functions, different reading order
// pipe is generally more readable for most developers
Practical Example: Text Processing Pipeline
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
const trim = str => str.trim();
const removeExtraSpaces = str => str.replace(/\s+/g, " ");
const addPeriod = str => str.endsWith(".") ? str : str + ".";
const cleanSentence = pipe(
trim,
removeExtraSpaces,
capitalize,
addPeriod
);
console.log(cleanSentence(" hello world ")); // "Hello world."
console.log(cleanSentence(" javaScript is fun ")); // "JavaScript is fun."
console.log(cleanSentence("already done.")); // "Already done."
Practical Example: Data Transformation Pipeline
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const filterActive = users => users.filter(u => u.active);
const sortByName = users => [...users].sort((a, b) => a.name.localeCompare(b.name));
const formatNames = users => users.map(u => `${u.name} (${u.role})`);
const joinWithComma = arr => arr.join(", ");
const getActiveUserSummary = pipe(
filterActive,
sortByName,
formatNames,
joinWithComma
);
const users = [
{ name: "Zara", role: "admin", active: true },
{ name: "Ali", role: "user", active: false },
{ name: "Mina", role: "editor", active: true },
{ name: "Bob", role: "user", active: true }
];
console.log(getActiveUserSummary(users));
// "Bob (user), Mina (editor), Zara (admin)"
Point-Free Style
// Point-free: function is defined without explicitly mentioning its arguments
// NOT point-free (mentions 'x')
const doubleNums = arr => arr.map(x => x * 2);
// Point-free (no mention of the data)
const double = x => x * 2;
const doubleAll = arr => arr.map(double); // still references 'arr'
// Truly point-free with composition
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const map = fn => arr => arr.map(fn);
const filter = fn => arr => arr.filter(fn);
const isEven = x => x % 2 === 0;
const doubleIt = x => x * 2;
// No mention of data at all — completely point-free
const getDoubledEvens = pipe(
filter(isEven),
map(doubleIt)
);
console.log(getDoubledEvens([1, 2, 3, 4, 5, 6])); // [4, 8, 12]
Common Mistakes
- Mixing up
pipeandcomposeargument order —pipe(f, g, h)(x)runsffirst;compose(f, g, h)(x)runshfirst. Pick one convention per codebase and stick with it. - Composing functions with incompatible signatures — each step must produce a value shaped like the next step's input. A function that returns
voidor a different type breaks the pipeline. - Trying to compose impure functions and expecting predictable results — composition assumes each step is a pure transformation. Mutation inside one step will leak into every downstream step.
Interview Questions
Q: What is function composition?
Function composition is combining two or more functions so the output of one becomes the input of the next. Given functions
fandg, composition produces a new function wherecompose(f, g)(x)equalsf(g(x)).
Q: What is the difference between pipe and compose?
composeapplies functions right-to-left:compose(f, g, h)(x)=f(g(h(x))).pipeapplies functions left-to-right:pipe(h, g, f)(x)=f(g(h(x))). Same result, different reading order.pipeis generally considered more readable.
Q: What is point-free style?
Point-free style is defining functions without explicitly mentioning the data (arguments) they operate on. Instead of
x => double(x), you writedoubledirectly. It's achieved through composition and curried helper functions.
Q: Implement a pipe function.
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);— takes an array of functions, returns a new function that passes its argument through each function left-to-right.
Q: Implement a compose function.
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);— identical shape topipebut usesreduceRightso functions apply right-to-left, matching the mathematicalf(g(h(x)))order.
Quick Reference — Cheat Sheet
FUNCTION COMPOSITION — QUICK MAP
Definition:
Combine fns so output of one feeds the next.
compose(f, g)(x) = f(g(x))
pipe(f, g)(x) = g(f(x))
One-line implementations:
const compose = (...fns) => x =>
fns.reduceRight((acc, fn) => fn(acc), x);
const pipe = (...fns) => x =>
fns.reduce((acc, fn) => fn(acc), x);
Reading order:
pipe left -> right (reads like the steps run)
compose right -> left (matches math f(g(h(x))))
Requirements for clean composition:
- Each fn is unary (takes one arg)
- Each fn's output matches the next fn's input shape
- Functions are pure (no hidden side effects)
Point-free style:
Build fns from other fns without naming the data.
pipe(filter(isEven), map(double)) // no 'x', no 'arr'
Previous: Function Currying Next: Debouncing & Throttling
This is Lesson 6.6 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.