Currying and Partial Application
Currying transforms a function that takes many arguments at once into a chain of functions that each take a single argument. Partial application is the related trick of fixing some arguments now and supplying the rest later. Both rely on closures, and both make tiny, reusable specialised functions out of generic ones.
The two ideas, side by side
// Regular function — three args at once. const add3 = (a, b, c) => a + b + c; add3(1, 2, 3); // 6 // Curried version — one arg at a time. const add3c = a => b => c => a + b + c; add3c(1)(2)(3); // 6 // Partial application — fix some args, return a new function. const addOne = add3.bind(null, 1); addOne(2, 3); // 6
Currying is a shape — a chain of single-argument functions. Partial application is an action — turning a function of N arguments into one of fewer. Curried functions are naturally partial-applicable.
Why this is useful
Configuration before use — write a generic helper, lock in the config, hand the rest to callers.
Cleaner pipelines —
pipe(filter(isAdult), map(getName))reads better than re-wrapping callbacks each time.Reuse without inheritance — partials create specialised functions without classes.
A real example — logging
const log = level => tag => message =>
console.log("[" + level + "] " + tag + ": " + message);
const info = log("INFO");
const error = log("ERROR");
const httpInfo = info("HTTP");
const dbError = error("DB");
httpInfo("200 OK");
dbError("connection refused");[INFO] HTTP: 200 OK [ERROR] DB: connection refused
Manual currying
Writing the chain by hand is fine for two or three arguments. For more, write a helper.
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...more) => curried(...args, ...more);
};
}
const sum = (a, b, c) => a + b + c;
const cs = curry(sum);
cs(1, 2, 3); // 6
cs(1)(2)(3); // 6
cs(1, 2)(3); // 6
cs(1)(2, 3); // 6The helper collects arguments across calls. Once it has enough — fn.length is the function's declared arity — it invokes the original.
Partial application with bind
Function.prototype.bind was built for this. The first argument is this; the rest pre-fill the first arguments.
function fetchJson(method, url, body) {
return fetch(url, { method, body: body && JSON.stringify(body) })
.then(r => r.json());
}
const get = fetchJson.bind(null, "GET");
const post = fetchJson.bind(null, "POST");
get("/api/me");
post("/api/login", { user: "ada" });Argument order matters
Partial application is easier when the parameters most likely to be fixed come first. Compare:
// Awkward — the data is what changes most, but it is first. const map = (arr, fn) => arr.map(fn); // Friendlier for partial application — fn first, then data. const mapC = fn => arr => arr.map(fn); const upperAll = mapC(s => s.toUpperCase()); upperAll(["hi", "there"]); // ["HI", "THERE"]
Libraries like Ramda put the data parameter last (called "data-last") for exactly this reason — every helper is naturally curry-friendly.
A peek at Ramda
Ramda is a functional library where every multi-arg function is auto-curried. You almost never call curry yourself.
Ramda style — illustrative
// import * as R from "ramda"; // const filterAdults = R.filter(p => p.age >= 18); // const getNames = R.map(p => p.name); // const adultNames = R.pipe(filterAdults, getNames); // adultNames(people);
The same pattern works with hand-rolled curry and pipe helpers — the library just provides them pre-built, along with hundreds of small data-last functions.
When to leave it alone
For a function called once, currying is noise.
add(1, 2, 3)beatsadd(1)(2)(3).Over-curried APIs make stack traces harder to read — each call has its own frame.
TypeScript types for deeply curried functions can get baroque.