On this page
Curryhigh-yield
Last reviewed 22 Sept 2026
Problem
Implement curry(fn) so a function of N arguments can be called with them one at a time, or in any grouping:
const add3 = (a, b, c) => a + b + c;const c = curry(add3);c(1)(2)(3); // 6c(1, 2)(3); // 6c(1)(2, 3); // 6c(1, 2, 3); // 6A common follow-up: sum(1)(2)(3)() → 6, with any number of calls ended by an empty call.
Clarifying questions
- Does
fnhave a fixed number of parameters (fn.length)? Yes for the main version. - Extra arguments beyond
fn.length? Pass them through. - Must the curried function keep
this? Nice to have. - Placeholders (
c(_, 2)(1, 3))? Only if asked — it is the hard follow-up.
Approach
Collect arguments in a closure. Each call adds its arguments to the collected list; once there are at least fn.length of them, call fn. Otherwise return a new function that keeps collecting. Every partial call returns a new function with its own argument list, so partial applications can be reused independently.
Step-by-step build
Step 1 — fixed arity
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) return fn.apply(this, args); return (...more) => curried.apply(this, [...args, ...more]); };}Step 2 — reuse is safe
const add1 = curry(add3)(1);add1(2)(3); // 6add1(10)(20); // 31 — add1 is not affected by the previous callThis works because every call builds a new array ([...args, ...more]) instead of pushing into a shared one.
Step 3 — the infinite sum variant
Keep adding until a call with no arguments.
function sum(a) { return function next(b) { return b === undefined ? a : sum(a + b); };}sum(1)(2)(3)(); // 6Final code
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) return fn.apply(this, args); const ctx = this; return function (...more) { return curried.apply(ctx, [...args, ...more]); }; };}
// sum(1)(2)(3)() → 6 ; also accepts several arguments per call: sum(1, 2)(3)()function sum(...first) { const total = first.reduce((s, x) => s + x, 0); return function next(...more) { return more.length === 0 ? total : sum(total, ...more); };}
// Placeholder version: `curry.placeholder` marks a slot to fill later.curry.placeholder = Symbol('_');function curryWithPlaceholder(fn) { const _ = curry.placeholder; return function curried(...args) { const ready = args.length >= fn.length && args.slice(0, fn.length).every((a) => a !== _); if (ready) return fn.apply(this, args); return (...more) => { const merged = args.map((a) => (a === _ && more.length ? more.shift() : a)); return curried.apply(this, [...merged, ...more]); }; };}Edge cases
fn.lengthignores default and rest parameters:(a, b = 1) => …has length 1. Accept an explicit arity argument if that matters.- A zero-argument function is called on the first call.
- Calling with more arguments than needed passes the extras to
fn. - The infinite
sumneeds an explicit terminator (the empty call). The alternative — overridingvalueOf/toStringso+sum(1)(2)works — is clever but fragile; mention it, do not rely on it.
Follow-ups
- Where is currying useful? Building specialised functions:
const logError = log('error'), event handlers with preset ids, point-freemap(multiply(2)). - Currying vs partial application: currying always takes one argument per step in its strict form; partial application fixes any number of arguments once (
binddoes that). - Uncurry: convert
a => b => c => …back into(a, b, c) => ….
Common mistakes
- Storing arguments in a single shared array, so reusing a partial application gives wrong results.
- Checking
args.length === fn.length, which breaks when extra arguments are passed. - Forgetting to return the function in the partial case.