Function Composition
MedFunction composition is the art of combining simple functions to build complex ones. Master currying, partial application, pipe/compose, and middleware patterns to write declarative, reusable code.
Interactive Visualization
1const add10 = x => x + 10;2const mult2 = x => x * 2;3const sub5 = x => x - 5;45const pipe = (...fns) =>6 x => fns.reduce((v, f) => f(v), x);78const transform = pipe(add10, mult2, sub5);9transform(5); // ((5+10)*2)-5 = 25
Key Points
- Currying transforms f(a, b, c) into f(a)(b)(c) - one arg at a time
- Partial application fixes some arguments, returns function for the rest
- pipe() flows left-to-right: pipe(f, g, h)(x) = h(g(f(x)))
- compose() flows right-to-left: compose(f, g, h)(x) = f(g(h(x)))
- Point-free style eliminates explicit parameter references
- Middleware chains handlers: request → handler1 → handler2 → response
Code Examples
Currying
// Manual currying const add = a => b => c => a + b + c; add(1)(2)(3); // 6 // Generic curry function function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } return (...more) => curried(...args, ...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
Currying enables partial application of any argument
Partial Application
// _.partial fixes arguments from the left function partial(fn, ...presetArgs) { return function(...laterArgs) { return fn(...presetArgs, ...laterArgs); }; } const greet = (greeting, name) => `${greeting}, ${name}!`; const sayHello = partial(greet, 'Hello'); sayHello('Alice'); // "Hello, Alice!" sayHello('Bob'); // "Hello, Bob!" // Real-world: pre-configure API calls const fetchJSON = partial(fetch, { headers: { 'Content-Type': 'application/json' } });
Partial fixes some args, unlike curry which transforms the signature
Pipe and Compose
// pipe: left-to-right (reading order) const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x); // compose: right-to-left (math notation) const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x); // Example functions const add10 = x => x + 10; const multiply2 = x => x * 2; const subtract5 = x => x - 5; // pipe: 5 → add10 → multiply2 → subtract5 pipe(add10, multiply2, subtract5)(5); // ((5+10)*2)-5 = 25 // compose: subtract5 ← multiply2 ← add10 ← 5 compose(subtract5, multiply2, add10)(5); // ((5+10)*2)-5 = 25
Pipe reads naturally; compose matches mathematical notation f∘g
Point-Free Style
// With explicit parameters const getNames = users => users.map(user => user.name); // Point-free (no explicit parameters) const prop = key => obj => obj[key]; const map = fn => arr => arr.map(fn); const getName = prop('name'); const getNames = map(getName); // Usage const users = [{ name: 'Alice' }, { name: 'Bob' }]; getNames(users); // ['Alice', 'Bob'] // Point-free with pipe const getActiveNames = pipe( filter(prop('active')), map(prop('name')) );
Point-free eliminates intermediate variables for cleaner pipelines
_.once() - Execute Once
function once(fn) { let called = false; let result; return function(...args) { if (!called) { called = true; result = fn.apply(this, args); } return result; }; } // Usage: expensive initialization const initializeApp = once(() => { console.log('Initializing...'); return { config: loadConfig() }; }); initializeApp(); // "Initializing..." + returns config initializeApp(); // returns cached config (no log) initializeApp(); // returns cached config (no log)
Once ensures a function runs exactly once, caching the result
Middleware Pattern
// Express-style middleware function createApp() { const middlewares = []; return { use(fn) { middlewares.push(fn); }, async handle(req) { let idx = 0; const next = async () => { if (idx < middlewares.length) { await middlewares[idx++](req, next); } }; await next(); return req; } }; } const app = createApp(); app.use(async (req, next) => { req.start = Date.now(); await next(); }); app.use(async (req, next) => { req.user = await getUser(req); await next(); }); app.use(async (req, next) => { req.result = process(req); }); await app.handle({ path: '/api' });
Middleware chains functions with next() control flow
Common Mistakes
- Confusing curry (transforms signature) with partial (fixes args)
- Forgetting that pipe and compose return functions, not values
- Over-using point-free style when it hurts readability
- Not handling async functions in compose/pipe
Interview Tips
- Implement curry() that handles any arity
- Implement pipe() and compose() - know the difference
- Explain when to use currying vs partial application
- Write a middleware system from scratch
Practice Problems
Apply this concept by solving these 6 problems
Implement Curry
MedTransform f(a,b,c) to f(a)(b)(c)
Implement Compose
MedCombine functions right-to-left
Implement Pipe
MedCombine functions left-to-right
Curry with Placeholder
HardCurry with placeholder support
Implement _.partial()
MedPartial application of functions
Implement _.once()
EasyFunction that runs only once