Currying and partial application
Currying: transform an n-arg function into a chain of unary functions — `f(a, b, c)` becomes `f(a)(b)(c)`. Partial application: pre-fix some args, returning a function expecting the rest — `f.bind(null, a)`. Both rely on closures. Useful for point-free style, event handlers per item, and composition. Overused, they hurt readability.
Two related but distinct ideas, both grounded in [[closures-and-lexical-scoping]].
Partial application
Bind some arguments now, supply the rest later:
function add(a, b, c) { return a + b + c; }
const addFive = add.bind(null, 5);
addFive(2, 3); // 10
// or with a helper:
const partial = (fn, ...pre) => (...rest) => fn(...pre, ...rest);
const greet = (greeting, name) => greeting + ", " + name;
const hi = partial(greet, "Hi");
hi("Sam"); // "Hi, Sam"Currying
Transform a multi-arg function into a chain of unary calls:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn(...args);
return (...more) => curried(...args, ...more);
};
}
const sum3 = curry((a, b, c) => a + b + c);
sum3(1)(2)(3); // 6
sum3(1, 2)(3); // 6
sum3(1)(2, 3); // 6Each call returns a function that captures the args supplied so far — pure closure magic.
Difference in one line
- Partial:
f(a, b, c)→f(a)(b, c). Pre-fills some args once. - Curry:
f(a, b, c)→f(a)(b)(c). Transforms the signature systematically.
Why bother
- Reusable handlers:
function ItemList({ items, onSelect }) {
return items.map((item) => (
<button key={item.id} onClick={() => onSelect(item.id)}>{item.name}</button>
));
}
// vs.
const onSelectId = (id) => () => onSelect(id); // curried form(Note: the inline arrow is fine for small lists; for very large lists, the curried form lets you memoize per id and skip allocating a new function per render.)
- Composition / point-free style (Ramda, lodash/fp):
const isAdult = pipe(prop("age"), gte(18));- Configuration of generic helpers:
const logger = (level) => (msg) => console.log(`[${level}]`, msg);
const error = logger("ERROR");
error("boom");When it hurts
- Stack traces get noisier (more anonymous frames).
- Readability for newcomers —
f(a)(b)(c)reads worse thanf(a, b, c)unless the team is fluent. - Debugging — break-pointing inside curried chains is annoying.
- Bundle size if you import all of
ramdafor one thing.
Use it where it pays — handlers, composition, fp pipelines — not as a default style.
Interview framing
"Partial application pre-fixes some arguments; currying transforms an n-arg function into a chain of unary calls. Both rely on closures to remember the supplied args. bind does partial application. A 10-line curry helper covers the rest. I use them for reusable handlers, pipe/compose pipelines, and configurable helpers — not as a default style, because debugging and readability take a hit when overused."
Follow-up questions
- •Implement curry from scratch.
- •How does Function.prototype.bind relate?
- •When would you prefer partial over curry?
Common mistakes
- •Mixing up the two terms.
- •Currying functions with variadic arity — fn.length lies for default/rest params.
- •Allocating new curried functions in every render.
Performance considerations
- •Each call in a curry chain allocates a closure. Trivial for handlers; measurable in tight loops. Memoize curried functions per stable key if rendering large lists.
Edge cases
- •Default parameters change fn.length.
- •Rest parameters make fn.length 0.
- •this binding — arrow vs regular.
Real-world examples
- •Ramda / lodash/fp pipelines, Redux compose / applyMiddleware, React event handlers per row.