JavaScript Currying Explained: Partial Application Guide
Currying transforms a multi-argument function into a chain of single-argument functions. How currying and partial application work in JavaScript.
Currying is the technique of transforming a function that takes multiple arguments into a sequence of functions that each take a single argument. Instead of calling add(2, 3), a curried version is called as add(2)(3) — each call returns a new function until all the arguments have been supplied.
A basic example
Here’s an ordinary two-argument function and its curried equivalent:
function add(a, b) {
return a + b;
}
function curriedAdd(a) {
return function (b) {
return a + b;
};
}
add(2, 3); // 5
curriedAdd(2)(3); // 5
curriedAdd(2) doesn’t compute anything yet — it returns a new function that has “remembered” a = 2 via a closure, and is waiting for b. Calling that returned function with 3 finally produces the result. Every curried function is built out of closures: each nested function captures the arguments already supplied by the outer calls.
Currying vs partial application
The two terms get used loosely, but they describe slightly different things:
- Currying always transforms a function into a chain of unary (single-argument) functions.
add(2)(3)(4)is fully curried — every call takes exactly one argument. - Partial application fixes some number of arguments up front and returns a new function expecting the rest, without necessarily reducing everything down to one argument at a time.
partial(add, 2)might return a function that still takes two more arguments at once.
In practice, most JavaScript utilities that people call “curry” functions are closer to partial application — they let you supply arguments in stages, but don’t force strictly one argument per call.
Why bother: building specialized functions
The practical value of currying is generating reusable, specialized functions from a general one:
function multiply(a) {
return function (b) {
return a * b;
};
}
const double = multiply(2);
const triple = multiply(3);
double(5); // 10
triple(5); // 15
multiply is a general-purpose function; double and triple are specialized versions created by supplying the first argument ahead of time. This pattern shows up constantly in functional-style code — configuring a logger with a prefix, building validators with a fixed rule, or preparing an event handler with context baked in:
const on = (eventName) => (handler) => element.addEventListener(eventName, handler);
const onClick = on("click");
onClick(() => console.log("clicked"));
A generic curry helper
Rather than hand-writing nested functions for every case, a generic curry helper can convert any function:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...more) => curried.apply(this, [...args, ...more]);
};
}
const curriedAdd3 = curry((a, b, c) => a + b + c);
curriedAdd3(1)(2)(3); // 6
curriedAdd3(1, 2)(3); // 6
curriedAdd3(1, 2, 3); // 6
This relies on fn.length, which reports how many named parameters a function declares — the helper keeps collecting arguments across calls until it has at least that many, then invokes the original function with all of them at once. Note this only works for functions with a fixed number of named parameters; it doesn’t work with rest parameters or default values, since those don’t count toward fn.length.
Where currying fits, and where it doesn’t
Currying is common in functional-programming-flavored codebases and libraries built around function composition, where chaining small, specialized functions together reads more clearly than passing around large configuration objects. It pairs naturally with array methods and pipeline-style code.
It’s a poor fit for APIs with optional parameters, named arguments, or configuration objects — currying assumes a fixed, ordered argument list, and forcing an options-bag-style function into curried form usually makes it harder to use, not easier. It also adds a layer of indirection that can slow down debugging for developers unfamiliar with the pattern, since a curried call like on("click")(handler) requires understanding closures to read comfortably at all.
If you’re working in TypeScript, typing a fully generic curry helper correctly is notoriously fiddly — generics can express it, but most teams are better served by writing out the specific curried functions they need rather than a fully generic, type-safe curry utility.
The takeaway
Currying turns a multi-argument function into a chain of single-argument functions, built on closures that capture each argument as it’s supplied. It’s most useful for generating specialized functions from a general one — a multiply(2) that becomes double, an event binder that becomes onClick — and less useful for APIs that already take optional or named parameters. Understand it as a tool for function composition, not a technique to reach for by default.
Tagged
Keep reading
Takina · · 5 min read Promise.all() vs allSettled() vs race() Compared
Promise.all() fails fast, allSettled() waits for every result, and race() returns whichever promise finishes first — how to choose correctly.
Takina · · 4 min read JavaScript Spread vs Rest Operators, Explained
The spread operator (...) expands an iterable into individual elements; the rest operator collects elements back into an array. Same syntax, opposite jobs.
Takina · · 4 min read ResizeObserver API Explained: Watching Element Size
The ResizeObserver API lets JavaScript watch an element's box size and react without polling or resize-event hacks. How it works and when to use it.