Currying and Partial Application
Curry vs partial application — unary chains, bind, practical helpers, and when extra abstraction hurts readability.
- javascript
- currying
- partial-application
- functions
People conflate currying and partial application. Both produce functions with fewer free arguments; only one systematically turns f(a,b,c) into f(a)(b)(c). Interviews like the distinction; production code likes the smaller idea: fix some args, reuse the rest.
Partial application
Fix the first arguments; call later with the rest.
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
double(5); // 10
// manual
function partial(fn, ...fixed) {
return (...rest) => fn(...fixed, ...rest);
}
const greet = (greeting, name) => `${greeting}, ${name}`;
const sayHi = partial(greet, 'Hi');
sayHi('Ada'); // "Hi, Ada"
bind is the language-built partial for this + leading args.
Currying
const curry2 = (fn) => (a) => (b) => fn(a, b);
const curry3 = (fn) => (a) => (b) => (c) => fn(a, b, c);
const volume = (l, w, h) => l * w * h;
const curried = curry3(volume);
curried(2)(3)(4); // 24
const areaWithHeight4 = curried(2)(3);
areaWithHeight4(4); // 24
A flexible curry accumulates until arity is met:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) return fn.apply(this, args);
return (...more) => curried.apply(this, args.concat(more));
};
}
function sum3(a, b, c) {
return a + b + c;
}
const cSum = curry(sum3);
cSum(1)(2)(3); // 6
cSum(1, 2)(3); // 6
cSum(1)(2, 3); // 6
Note: fn.length breaks with default params and rest params — real libraries handle that carefully.
Why teams use it
const map = curry((fn, arr) => arr.map(fn));
const doubleAll = map((x) => x * 2);
doubleAll([1, 2, 3]); // [2, 4, 6]
// point-free pipelines
const prices = [10, 20, 30];
const withTax = map((p) => p * 1.1);
withTax(prices);
Curried map/filter lets you build specialized helpers without nested lambdas everywhere — until it doesn’t, and names get cryptic.
Currying ≠ partial
| Currying | Partial | |
|---|---|---|
| Shape | Sequence of unary (or staged) calls | One function with some args fixed |
| Arity | Transforms multi-arg into chain | Reduces arity once |
| Example | f(a)(b)(c) |
g = f.bind(null, a) then g(b,c) |
You can implement partial using curry (curried(a) returns a partial). They are related, not identical.
When to skip it
// clearer
function fetchUser(id, { signal } = {}) {
return fetch(`/api/users/${id}`, { signal }).then((r) => r.json());
}
// unnecessary curry theater
const fetchUser = curry((id, opts) => /* ... */);
Don’t curry every helper. Do partially apply when the same config repeats (API base URL, logger prefix, i18n t bound to a namespace).
Interview answer
“Partial application fixes some arguments and returns a function for the rest — Function.prototype.bind is the built-in. Currying converts a multi-arg function into a chain of functions, each taking the next argument. I use partials for repeated config; I avoid heavy curry in app code when a named closure is clearer.”
Related
Placeholders and practical API design
Full placeholder curry (map(_, arr)) needs a library. Most app code only needs partial application of leading config:
const request = (base) => (path, init) =>
fetch(base + path, init).then((r) => r.json());
const api = request('https://api.example.com');
await api('/users');
await api('/posts', { method: 'POST', body });
That’s a closure factory — same power as curry, clearer call sites. Reach for multi-arity curry when building a shared FP toolkit; reach for factories when building product APIs teammates will call daily.
Further reading
Related guides
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Default ParametersES6 default parameter values — evaluated at call time, temporal dead zone with lets, and why undefined triggers defaults but null does not.
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.