curry helper that works on any function;Learn JS Series):Exercise 1 - a private-sum IIFE:
const shown = (function () {
let total = 0;
for (let i = 1; i <= 10; i++) total += i;
return total;
})();
console.log(shown); // 55
// i and total do not exist out here
The insight: the IIFE runs immediately and its locals vanish, so only the returned value escapes into shown.
Exercise 2 - a counter module:
const counter = (function () {
let count = 0;
return {
increment() { return ++count; },
decrement() { return --count; },
value() { return count; },
};
})();
console.log(counter.increment(), counter.increment(), counter.decrement()); // 1 2 1
The insight: count is private to the IIFE; the returned methods are the only way to touch it.
Exercise 3 - the revealing variant:
const counter2 = (function () {
let count = 0;
function increment() { return ++count; }
function decrement() { return --count; }
function value() { return count; }
return { increment, decrement, value }; // public API in one place
})();
console.log(counter2.increment()); // 1
The insight: listing the public names in one return makes the API obvious at a glance; a real ES module would give this privacy without the wrapper.
Last episode we saw how closures let an IIFE hide state and hand back a controlled public surface. Today we lean on that exact same closure machinery again, but point it at a different problem: how to shape a function's arguments. What we cover now, currying and partial application, are two closely related functional techniques that build directly on the bind partial application you already met in episode 23. Having said that, they are two of those ideas that sound academic until the moment they click, and then you start spotting places for them everywhere. Let's build them from the ground up.
Partial application means taking a function with several arguments and fixing some of them ahead of time, producing a new function that only needs the rest. You saw this with bind in episode 23; here we do it explicitly with a closure, which shows what is really happening under the hood:
function multiply(a, b) {
return a * b;
}
function partial(fn, fixedArg) {
return function (remaining) {
return fn(fixedArg, remaining); // 'fixedArg' is remembered via closure
};
}
const double = partial(multiply, 2);
const triple = partial(multiply, 3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
partial(multiply, 2) returns a function that has already locked in 2 as the first argument; you only supply the second. The fixed argument survives in the closure (episode 17), waiting patiently for the rest to arrive. This is exactly what multiply.bind(null, 2) does - only now you can see the closure machinery underneath instead of trusting bind to do it invisibly. Partial application is about pre-filling some arguments to specialize a general function.
Let me make the connection to bind concrete, because it is worth seeing the two side by side. These two lines produce functionally identical double helpers:
function multiply(a, b) { return a * b; }
const doubleViaBind = multiply.bind(null, 2); // built-in partial application
const doubleViaClosure = (b) => multiply(2, b); // hand-rolled, same effect
console.log(doubleViaBind(9), doubleViaClosure(9)); // 18 18
The null in bind is the this value (episode 23), which a plain function like multiply does not care about, so we pass null. Everything after that first argument is a fixed argument. So bind was doing partial application all along - we just did not have the vocabulary for it yet. Now we do.
Currying is a stricter, more systematic transformation (named after the logician Haskell Curry, which is also where the Haskell language got its name). To curry a function is to turn a function that takes N arguments all at once into a chain of N functions that each take exactly one argument, returning the next function until all are collected, then finally computing the result:
// a normal three-argument function:
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
// the curried version: one argument per call
function curriedAdd(a) {
return function (b) {
return function (c) {
return a + b + c; // all three captured via nested closures
};
};
}
console.log(curriedAdd(1)(2)(3)); // 6
Read curriedAdd(1)(2)(3) left to right and it is not mysterious at all: curriedAdd(1) returns a function waiting for b; calling that with 2 returns a function waiting for c; calling that with 3 finally has everything it needs and computes. Each nested function closes over the arguments gathered so far, which is why a is still visible three levels deep. Currying is about taking arguments one at a time through a chain of functions.
The difference from partial application is subtle but real. Partial application fixes some arguments and leaves a function that takes the rest all at once. Currying insists on exactly one argument per call, all the way down the chain. Put another way: currying is the disciplined, uniform version (always one arg, always a function back until the end), while partial application is the pragmatic "fix whatever you like, take the remainder together" version. They are cousins, not twins.
Currying looks like extra ceremony until you notice what it quietly hands you: every partially-applied stage is a reusable, specialized function. Because a curried function returns a new function after each argument, you can stop partway and reuse that intermediate function as many times as you like:
function curriedGreet(greeting) {
return function (name) {
return `${greeting}, ${name}!`;
};
}
const sayHi = curriedGreet("Hi"); // stop after the first argument
const sayBye = curriedGreet("Bye");
console.log(sayHi("scipio")); // "Hi, scipio!"
console.log(sayHi("alice")); // "Hi, alice!"
console.log(sayBye("scipio")); // "Bye, scipio!"
curriedGreet("Hi") gives you a reusable sayHi you can apply to a hundred different names without ever repeating the greeting. This is the everyday value of currying: it turns configuration into specialized functions naturally. You configure once (the greeting) and reuse forever (the name). It also composes beautifully with the pipelines we build next episode, because curried functions plug into one another cleanly - a function that always takes one argument and returns one value is the ideal Lego brick.
Here is a slightly more realistic taste of that specialization, one you will genuinely reach for. Imagine a logger that needs a level and a message. Curry it, and each log level becomes its own tidy function:
const log = (level) => (message) => `[${level.toUpperCase()}] ${message}`;
const info = log("info"); // pre-configure the level once
const warn = log("warn");
const error = log("error");
console.log(info("server started")); // "[INFO] server started"
console.log(warn("disk almost full")); // "[WARN] disk almost full"
console.log(error("connection lost")); // "[ERROR] connection lost"
Notice how info, warn, and error read like purpose-built helpers, yet there is only ONE tiny log function doing the real work. That is currying earning its keep: less repetition, clearer intent.
Writing nested functions by hand for every case is tedious, and nobody wants to hand-roll a three-deep closure every time. So we write a single curry helper that turns any function into a curried one automatically. The trick is delightfully small: keep collecting arguments until we have as many as the original function expects (its .length property, which is the number of declared parameters), then call it for real:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args); // enough args -> call the real function
}
return (...more) => curried(...args, ...more); // otherwise gather more
};
}
function add3(a, b, c) {
return a + b + c;
}
const curriedAdd3 = curry(add3);
console.log(curriedAdd3(1)(2)(3)); // 6
console.log(curriedAdd3(1, 2)(3)); // 6 - flexible: any grouping works
console.log(curriedAdd3(1)(2, 3)); // 6
console.log(curriedAdd3(1, 2, 3)); // 6 - even all at once
This curry is wonderfully flexible: it accepts arguments in any grouping, gathering them across calls until it finally has enough. fn.length gives the expected count (for add3 that is 3), and the rest/spread operators ...args and ...more (from episode 20) do all the collecting. The named function expression curried lets the inner arrow call back into itself recursively (episode 24) - so this one little helper is closures, plus recursion, plus rest/spread, all working together. It is a genuine showcase of everything we have learned this phase.
Let me trace one call slowly so the recursion is not a black box. Take curriedAdd3(1)(2)(3):
// step 1: curriedAdd3(1)
// args = [1], fn.length = 3, 1 >= 3 is false
// -> returns (...more) => curried(1, ...more)
// step 2: (...)(2)
// args = [1, 2], 2 >= 3 is false
// -> returns (...more) => curried(1, 2, ...more)
// step 3: (...)(3)
// args = [1, 2, 3], 3 >= 3 is TRUE
// -> returns fn(1, 2, 3) === 6
console.log(curry((a, b, c) => a + b + c)(1)(2)(3)); // 6
Each call that does not yet have enough arguments returns another gathering function; the call that finally reaches the threshold invokes the real fn. That is the whole mechanism. Read it twice and it will never look like magic again. ;-)
Currying really shines with small "accessor" style functions - the kind you feed to map, filter, and friends (episode 18). Here is a curried property getter that produces reusable accessors:
// a curried "prop getter"
const prop = (key) => (obj) => obj[key];
const getName = prop("name");
const users = [{ name: "scipio" }, { name: "alice" }];
console.log(users.map(getName)); // ["scipio", "alice"] - clean and reusable
That prop("name") builds a getName function once, and then map applies it across the whole array. Compare it to writing users.map(u => u.name) inline - for a single use the inline arrow is fine, but the moment you need getName in three places, the curried version pays off by giving you a named, reusable thing. This is exactly the kind of spot where currying is not showing off; it is genuinely tidier.
Since quite a few of you arrived here from the Learn Python Series (with some Rust and Go readers along for the ride), a look sideways is illuminating, because JavaScript sits in an interesting middle ground on this topic.
Haskell - the language named after the very Curry we mentioned - curries everything by default. In Haskell there is no such thing as a two-argument function; a function of "two arguments" is really a function that takes one argument and returns a function taking the next. Partial application is therefore free and pervasive: you just supply fewer arguments than the "full" count and you get a specialized function back. JavaScript has to emulate with closures what Haskell gives you as the ground truth of the language.
Python has no built-in currying, but its standard library ships functools.partial, which is partial application made official. It reads very naturally:
from functools import partial
def multiply(a, b):
return a * b
double = partial(multiply, 2) # fix the first argument
print(double(5)) # 10
That partial(multiply, 2) is the Python twin of our JavaScript partial helper and of multiply.bind(null, 2) - same idea, different spelling. Python programmers reach for functools.partial exactly where a JS programmer reaches for bind or a curried helper.
Rust does not curry either (its functions have a fixed arity), but closures capture their environment and give you the same specialization, much like our hand-rolled JS version:
fn main() {
let multiply = |a: i32, b: i32| a * b;
let double = move |b: i32| multiply(2, b); // closure fixes the first arg
println!("{}", double(5)); // 10
}
The move closure captures multiply and pins 2 in place - conceptually identical to what our JavaScript closure does. Go is even more spartan: no currying, no partial in the standard library, but you can return a closure from a function to get partial application by hand, the same way we did at the top of this episode. The pattern travels; only the syntax changes.
The takeaway: currying-as-a-default is a functional-language idea (Haskell, ML), while most mainstream languages - JavaScript very much included - reach the same practical benefits through closures and partial application. Understanding the closure underneath means you can reproduce this technique in almost any language you meet.
Now for the honest part, because I would be doing you a disservice to sell these as always-good. Currying and heavy partial application are powerful in a functional style, and they read beautifully when you are building pipelines of small, composable functions (Phase 10 leans on them hard). But over-currying ordinary, everyday code makes it cryptic - f(a)(b)(c) is unfamiliar and faintly alien to many readers, and a wall of it will slow a teammate down rather than help them. Use these techniques where they genuinely earn their place:
sayHi from curriedGreet, info from log).For plain, one-off calls, a normal multi-argument function is clearer, full stop. multiply(2, 5) beats curry(multiply)(2)(5) every time when you only need it once. Like every tool in this series, currying is excellent in its place and pure noise everywhere else. Aim for clarity, not cleverness - the reader you are helping most is usually yourself, six months from now, trying to remember what on earth g(x)(y)(z) was supposed to do.
Three exercises, increasing in difficulty. Try to predict each result before you run it. Full solutions open the next episode.
curriedMultiply(a)(b)(c) that multiplies three numbers, using nested functions. Then create a timesSix = curriedMultiply(2)(3) intermediate and call it with several different values.curry(fn) helper from this episode and use it on a function volume(l, w, h). Show that curry(volume)(2)(3)(4), curry(volume)(2, 3)(4), and curry(volume)(2, 3, 4) all give the same answer.prop(key)(obj) getter and use map to extract the city from an array of user objects. Then explain, in one sentence, the difference between currying (one argument per call) and partial application (fixing some arguments at once).bind from episode 23 does), all powered by closures.info, warn, error) and for composition.curry(fn) helper gathers arguments (using fn.length, rest/spread, and recursion) until it has enough, then calls the original, accepting any grouping of calls.functools.partial, Rust and Go use closures - the closure underneath is the portable idea.Next episode we put these curried, single-argument functions to work with function composition, building data pipelines by wiring small functions together into powerful ones - and you will see why currying and composition are such natural partners.