Currying & Partial Application
Currying and partial application are two closely related techniques for specializing functions — turning a general-purpose function into a more focused one by locking in some of its arguments ahead of time. Both lean on closures, both improve reusability, and both make code read more declaratively. They are frequently confused, so this page draws a sharp line between them and shows how to implement each in modern JavaScript.
Currying vs partial application
Currying transforms a function that takes N arguments into a chain of N functions, each taking exactly one argument. You call them one at a time, and the final call produces the result.
Partial application fixes some (one or more) of a function’s arguments up front, returning a new function that takes the remaining arguments — all at once. There is no requirement that you supply one argument per call.
| Aspect | Currying | Partial application |
|---|---|---|
| Arity per call | Exactly one argument | Any number of arguments |
| Result of a call | Another function until all args given | A function expecting the rest |
| Shape | f(a)(b)(c) | f(a, b) then g(c) |
| Built-in support | None (write a helper) | Function.prototype.bind |
Manual currying with closures
The simplest curry is hand-written: each function returns the next, capturing the prior argument in its closure.
const add = (a) => (b) => (c) => a + b + c;
console.log(add(1)(2)(3)); // fully applied
const add10 = add(10); // a fixed at 10
console.log(add10(2)(3)); // reuse the partially-built function
Output:
6
15
Each arrow returns a function until the last one, which has all three values in scope and can finally compute.
A generic curry helper
Writing nested arrows by hand is tedious for real functions. A reusable curry accepts a normal multi-argument function and returns a curried version that collects arguments until it has enough, then invokes the original.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...next) => curried.apply(this, [...args, ...next]);
};
}
const volume = (l, w, h) => l * w * h;
const curriedVolume = curry(volume);
console.log(curriedVolume(2)(3)(4)); // one at a time
console.log(curriedVolume(2, 3)(4)); // mixed grouping
console.log(curriedVolume(2, 3, 4)); // all at once
Output:
24
24
24
The helper uses fn.length (the function’s declared arity) to know when enough arguments have arrived. A flexible curry like this also tolerates supplying several arguments per call, blurring the line toward partial application.
Note:
fn.lengthdoes not count rest parameters or parameters with default values. For variadic functions, prefer explicit partial application over a generic curry.
Partial application with bind
Function.prototype.bind is the native, zero-dependency way to partially apply. Its first argument sets this; every argument after that is pre-filled from the left.
function request(method, url, body) {
return `${method} ${url} ${JSON.stringify(body)}`;
}
const post = request.bind(null, "POST");
const postToUsers = request.bind(null, "POST", "/api/users");
console.log(post("/api/logs", { level: "info" }));
console.log(postToUsers({ name: "Ada" }));
Output:
POST /api/logs {"level":"info"}
POST /api/users {"name":"Ada"}
A reusable partial helper
When you want clearer intent than bind(null, ...), a tiny partial helper reads better and skips the this argument entirely.
const partial = (fn, ...fixed) => (...rest) => fn(...fixed, ...rest);
const greet = (greeting, name) => `${greeting}, ${name}!`;
const sayHi = partial(greet, "Hi");
console.log(sayHi("Grace"));
console.log(sayHi("Linus"));
Output:
Hi, Grace!
Hi, Linus!
Practical uses
These techniques shine when you build small, configured functions once and reuse them across a codebase:
- Event handlers:
partial(handleClick, itemId)produces a handler bound to a specific item without an inline arrow on every render. - Pipelines: Curried, single-argument functions slot directly into
compose/pipe, since each stage takes exactly one value. - Configuration: Lock in a logger level, an API base URL, or a currency formatter once, then call the specialized version everywhere.
const formatCurrency = (locale, currency) => (amount) =>
new Intl.NumberFormat(locale, { style: "currency", currency }).format(amount);
const usd = formatCurrency("en-US", "USD");
const eur = formatCurrency("de-DE", "EUR");
console.log(usd(1999.5));
console.log(eur(1999.5));
Output:
$1,999.50
1.999,50 €
Best Practices
- Reach for partial application via
bindfor everyday specialization — it is built in, fast, and obvious. - Use a generic
curryhelper mainly for fixed-arity, single-purpose functions destined for composition pipelines. - Order parameters most-stable first, data last so the value you vary most is supplied last (the “data-last” convention used by libraries like Ramda).
- Avoid currying variadic functions or those with default parameters;
fn.lengthwill mislead the helper. - Give specialized functions descriptive names (
postToUsers,usd) so call sites stay readable. - Do not over-curry: deeply nested single-argument chains can hurt clarity more than they help.