Currying
Currying is a technique for reshaping how a function takes its arguments. It shows up across many languages, not only JavaScript, and it pairs naturally with closures and higher-order functions.
The core idea is small: currying rewrites a function so that f(a, b, c) becomes callable as f(a)(b)(c). Same computation, different call shape.
One thing to fix in your head early: currying does not run your function. It hands back a new function that, once you feed it all the arguments, calls the original. It is a transform, not an invocation.
We’ll start with a concrete example so the shape is obvious, then get to the part that matters — why anyone would bother.
A minimal curry
Let’s build a helper curry(f) that curries a two-argument function f. Given f(a, b), it produces something you call as f(a)(b).
function curry(f) { // curry(f) does the currying transform
return function(a) {
return function(b) {
return f(a, b);
};
};
}
// usage
function area(width, height) {
return width * height;
}
let curriedArea = curry(area);
alert( curriedArea(4)(5) ); // 20
There’s nothing exotic here: two nested wrappers, and closures doing the remembering.
curry(func)returns a wrapper,function(a).- Call it as
curriedArea(4)and the4gets captured in that wrapper’s lexical environment. It returns a second wrapper,function(b). - Call that with
5, and now both values are in scope, so it forwards the call to the originalarea(4, 5).
Each pair of parentheses peels off one argument and hands back a function waiting for the next. The value you passed doesn’t vanish — the closure keeps it alive until the final call needs it.
Try peeling the arguments off yourself below. Each button applies one more argument to the chain — watch how the first value stays captured while the second one is still missing.
Fully featured currying helpers, like _.curry from lodash, are more forgiving. They return a wrapper you can call either way — all at once, or one argument at a time:
function area(width, height) {
return width * height;
}
let curriedArea = _.curry(area); // using _.curry from lodash library
alert( curriedArea(4, 5) ); // 20, still callable normally
alert( curriedArea(4)(5) ); // 20, called partially
We’ll build a version with that flexibility ourselves further down.
Currying? What for?
The payoff isn’t the funny call syntax. It’s partial application: locking in some arguments now and getting back a smaller, more specific function for later.
A realistic example makes this land. Say you have a messaging function post(channel, author, text) that formats and prints a line. A real app would ship the message over the network; here we’ll keep it to an alert:
function post(channel, author, text) {
alert(`#${channel} @${author}: ${text}`);
}
Curry it:
post = _.curry(post);
Because this is the flexible kind of curry, post still works exactly as before when you pass everything at once:
post("general", "Maya", "hello everyone"); // post(a, b, c)
And it also accepts arguments one group at a time:
post("general")("Maya")("hello everyone"); // post(a)(b)(c)
Now the useful move. Every message you send this session goes to the same channel, so pin it down once:
// inGeneral is the partial of post with the first argument fixed
let inGeneral = post("general");
// use it
inGeneral("Maya", "message"); // #general @Maya: message
inGeneral is post with its first argument already supplied — a partially applied function, or “partial” for short. Every call through it reuses that captured channel.
You can specialize a second time. Fix the author too, and you’ve got a dedicated sender for one person:
let mayaInGeneral = inGeneral("Maya");
mayaInGeneral("message"); // #general @Maya: message
Two things worth pulling out:
- Nothing was given up. Curried
postis still callable the normal, all-at-once way. - Building narrow helpers like “post to general” or “Maya posting to general” costs one line each.
post(channel, author, text)
inGeneral(author, text) — channel = “general”
mayaInGeneral(text) — channel = “general”, author = “Maya”
Here’s the same partial-application idea you can drive yourself. A curried multiply(a, b) becomes a source of specialized helpers: fix the first argument once and you get double, triple, or a scaler of your own. Notice that each helper keeps reusing its captured multiplier.
Advanced curry implementation
Here’s a curry that handles functions of any fixed length and allows the flexible calling style we used with lodash — normal, partial, or fully chained.
It’s short:
function curry(func) {
return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
Try it out:
function total(a, b, c) {
return a + b + c;
}
let curriedTotal = curry(total);
alert( curriedTotal(1, 2, 3) ); // 6, still callable normally
alert( curriedTotal(1)(2,3) ); // 6, currying of 1st arg
alert( curriedTotal(1)(2)(3) ); // 6, full currying
The demo below runs exactly this implementation. Pick a call style and watch the wrapper either collect more arguments or, once it has three, run total and return the sum — all three routes land on the same 6.
It looks denser than the two-wrapper version, but the logic is a single decision repeated.
curry(func) returns the wrapper curried:
// func is the function to transform
function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
return function(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
};
Every time you call curried, it checks how many arguments it has gathered so far against func.length — the number of parameters the original function declares. Two branches follow:
- Enough arguments (
args.length >= func.length): stop collecting and run the real thing withfunc.apply(this, args). Passingthisthrough means the transform survives being used as a method. - Not enough yet: don’t call
func. Return a fresh function that waits for more arguments (args2), then re-invokescurriedwith everything merged —args.concat(args2). That re-entry runs the same check again.
So each call either finishes with a result or gathers more and hands back another collector. It’s recursion driven by argument count.
call curried(…args)
Walk through curriedTotal(1)(2)(3) with func.length === 3:
curriedTotal(1)— 1 argument, fewer than 3 → returns a collector.(2)— merged args are[1, 2], still fewer than 3 → returns another collector.(3)— merged args are[1, 2, 3], now equal to 3 → callstotal(1, 2, 3)and yields6.
And curriedTotal(1)(2, 3) reaches the count in two calls instead of three, because the second call brings two arguments at once. Either path lands on the same func.apply.
Summary
Currying transforms a function so f(a, b, c) can be called as f(a)(b)(c). Practical JavaScript versions are lenient: they keep the normal call working and only return a partial when you haven’t supplied enough arguments yet.
The real value is easy partials. In the messaging example, currying the general post(channel, author, text) let us fix arguments as we went — post(channel) gave a sender for one channel, post(channel)(author) narrowed it to a single person — each one a small, focused function built from the same original.