Currying and partial application: fixing some arguments now, the rest later
Currying turns f(a, b, c) into f(a)(b)(c); partial application fixes some arguments up front. Learn both, fn.length and its limits, and write curry.
Comes up in: Coding
Learn#
Zara writes logEvent(app, level, message) and calls it 200 times as logEvent("checkout", "info", …). She wants checkoutInfo(message) without writing a new wrapper for every app and level.
Two tools do that. Partial application fixes some arguments now and returns a function for the rest: logEvent.bind(null, "checkout", "info"). Currying goes further: it turns a function of many arguments into a chain of calls that each take some arguments, until all are there — log("checkout")("info")("paid").
A function’s arity is how many arguments it expects. Currying needs a fixed arity to know when to stop.
A generic curry reads it from fn.length — but fn.length counts only parameters before the first default or rest parameter.
const curry = (fn) => function curried(...args) {
return args.length >= fn.length ? fn.apply(this, args) : (...more) => curried.apply(this, [...args, ...more]);
};
const price = (rate, qty, unit) => rate * qty * unit;
const c = curry(price);
console.log(c(2)(3)(4), c(2, 3)(4), c(2)(3, 4));
const double = c(2);
console.log(double(5)(10));
console.log(((a, b = 2) => 0).length, ((...xs) => 0).length);Output (verified)
24 24 24 100 1 0
collected
empty
needed
- 3
c = curry(price). price needs 3 arguments (price.length is 3).
Read the steps as text
- c = curry(price). price needs 3 arguments (price.length is 3).
- c(2): only 1 argument so far. curry returns a new function that remembers [2].
- (3): now [2, 3]. Still not enough, so another function comes back.
- (4): [2, 3, 4] is enough. curry calls price(2, 3, 4) and returns 24.
Predict#
Predict first
const f = curry((a, b = 10) => a + b); What is f(1)?
fn.length is 1, because parameters with defaults are not counted. One argument is enough, so f(1) calls the function at once and b takes its default: 11. Default and rest parameters make fn.length-based currying unreliable.
Interview angle#
Say it like this — about 30 seconds
“Currying turns
f(a, b, c)into a chain of calls. Each call takes some arguments, and the original runs once it has enough. Partial application just fixes some arguments up front, likebinddoes. To implement curry, I return a function that collects arguments in a closure; when the count reachesfn.lengthit calls the function, otherwise it returns another collector.fn.lengthignores default and rest parameters. So a function with any number of arguments needs a set count or an end signal.”
Follow-up Implement sum(1)(2)(3)() that returns 6.
Follow-up What is placeholder currying?
f(_, 2)(1) fills the second argument first. The collector fills placeholders before appending new arguments.Follow-up When is currying not worth it?
(msg) => logEvent("checkout", "info", msg) — is clearer to read and debug than a curried chain.Practice#
Timed, like the real round
Implement curry(fn) for any grouping of arguments, and the classic sum(1)(2)(3)() puzzle.
Done when
curry(add3)gives 6 for(1)(2)(3),(1, 2)(3)and(1)(2, 3), and partial results are reusable.sum(1)(2)(3)()is 6, andsum()is 0.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
What does currying do?
Turns f(a, b, c) into calls that take arguments a few at a time, running f once all are collected.
How does partial application differ from currying?
It fixes some arguments once and returns a function for the rest.
What does fn.length count?
Parameters before the first default or rest parameter.
Write curry(fn) from memory.
Write it from memory, then run it against the tests in Practice.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
Partial application: fix some arguments now. Currying: keep accepting arguments until there are enough. Both rely on closures to remember what was given.
Check yourself: Is bind(null, x) currying or partial application?
Partial application.
How it works underneathSenior rounds
Each returned function keeps the arguments so far in its closure. Calling it builds a new array, so you can reuse partial results safely. fn.length is the function’s expected argument count as defined by its parameter list Source: MDN: Function.length.
Check yourself: Where does curry store the arguments collected so far?
In the closure of each returned function (the args array).
Edge cases and trapsWhere people slip
If the collector pushed into one shared array, const d = c(2); d(3)(4); d(5)(6) would mix the calls. Always build a new array. Calling with zero arguments, c(), adds nothing and returns another collector.
Check yourself: Why should a curried collector copy the args array instead of pushing to it?
Pushing would leak arguments between different branches of reuse.
In productionStaff-level answers
Ramda and lodash/fp curry every function, with the data last, so they join well with pipe. In TypeScript, typing a general curry is hard. Most teams use small arrow wrappers or bind instead Source: Ramda: curry.
Check yourself: Name a library that curries all its functions.
Ramda (also lodash/fp).
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- What is currying? Implement
curry.