#Functional#Polyfill

Curry Polyfill

Implement a curry function that transforms a multi-argument function into a chain of single (or partial) calls using fn.length.

By Pratik RaiMedium

Currying converts a function taking several arguments into a chain of functions each taking one. A generic curry utility goes further: it returns a function that collects arguments however they arrive and invokes the original once it has enough. It is a small piece of code that depends entirely on one property most people never think about — Function.prototype.length.

What currying gives you

JSfile.javascript
1const add = (a, b, c) => a + b + c; 2const curried = curry(add); 3 4curried(1)(2)(3); // 6 5curried(1, 2)(3); // 6 6curried(1)(2, 3); // 6 7curried(1, 2, 3); // 6

Every grouping produces the same result. The function accumulates arguments until it has as many as the original declared, then runs.

The practical payoff is specialisation — building a narrower function from a general one:

JSfile.javascript
1const log = curry((level, module, message) => `[${level}] ${module}: ${message}`); 2const error = log('ERROR'); 3const authError = error('auth'); 4 5authError('Invalid token'); // "[ERROR] auth: Invalid token"

Currying versus partial application

These are used interchangeably and are not the same thing.

Currying transforms a function of N arguments into N nested unary functions. It is a structural transformation of arity.

Partial application fixes some arguments and returns a function taking the rest. bind does partial application: fn.bind(null, 1, 2).

A curried function can be partially applied one argument at a time; partial application does not require currying. In practice, most "curry" utilities — including the one below — implement a hybrid that accepts either style, which is why the distinction gets blurred.

The implementation

JSfile.javascript
1function curry(fn) { 2 if (typeof fn !== 'function') { 3 throw new TypeError('curry expects a function'); 4 } 5 6 return function curried(...args) { 7 if (args.length >= fn.length) { 8 return fn.apply(this, args); 9 } 10 return function (...next) { 11 return curried.apply(this, [...args, ...next]); 12 }; 13 }; 14}

The whole design rests on fn.length — the number of parameters declared before the first default or rest parameter. When enough arguments have accumulated, invoke; otherwise return a collector that appends to what is already gathered.

Using a named function expression (curried) lets the inner closure recurse without depending on an outer binding. Both apply calls forward this, so the utility works on methods as well as free functions.

Where fn.length betrays you

Because arity detection is the mechanism, anything that makes length inaccurate breaks currying entirely:

Default parameters stop the count.

JSfile.javascript
1((a, b, c = 1) => 0).length; // 2, not 3

Curry it and it fires after two arguments, silently ignoring the third.

Rest parameters count as zero.

JSfile.javascript
1((...args) => 0).length; // 0

The curried version invokes immediately on the first call, defeating the purpose.

Destructured parameters count as one each, which is usually what you want, but is worth knowing.

For variadic functions you must pass the arity explicitly:

JSfile.javascript
1function curryN(fn, arity = fn.length) { 2 return function curried(...args) { 3 return args.length >= arity 4 ? fn.apply(this, args) 5 : (...next) => curried.apply(this, [...args, ...next]); 6 }; 7}

Any curry utility used on real-world code needs this escape hatch.

Placeholder support

Libraries like Ramda and Lodash let you skip an argument and fill it later:

JSfile.javascript
1const _ = curry.placeholder; 2const greet = curry((greeting, name) => `${greeting}, ${name}`); 3const greetAda = greet(_, 'Ada'); 4greetAda('Hello'); // "Hello, Ada"

Implementing it means scanning the accumulated arguments for placeholder tokens and substituting positionally rather than appending. It roughly doubles the complexity, and it is a common follow-up question precisely because it forces you to stop treating the argument list as a simple queue.

Costs worth being aware of

Every partial application allocates a closure. In a hot loop — per item over ten thousand rows — that overhead is measurable. Curry at module scope, not inside the loop.

Stack traces get worse. A bug inside a curried function shows a chain of anonymous frames rather than a clear call site.

arguments.length is not the same as fn.length. The first counts what was passed, the second what was declared. Currying depends on the second.

Common follow-ups

"Implement pipe and compose." The natural companion. Curried unary functions are exactly what those combinators need, which is where currying stops feeling academic.

"Curry an existing method." Tests whether your this forwarding is right — a curried method must still work when called on its owner.

"How would you type this in TypeScript?" Genuinely hard. Expressing "returns either the result or a function awaiting the remaining arguments" requires recursive conditional types, and most real-world typings cap out at a handful of arities.

Key takeaways

  • Currying splits N arguments into a chain; partial application fixes some and returns the rest.
  • The implementation is driven entirely by fn.length.
  • Default and rest parameters corrupt length — provide an explicit arity option.
  • Each partial application allocates a closure; keep it out of hot loops.
  • Placeholders require positional substitution, not simple appending.

Goal: Implement curry using fn.length as the arity target. Support mixed-arity calling styles.

Frequently asked questions

What is currying in JavaScript?
Currying turns a function that takes several arguments into a chain of functions that each take some of them, so a call like `sum(1)(2)(3)` works as well as `sum(1, 2, 3)`. The curried version collects arguments until it has enough, then runs the original function.
How does a curry implementation know when to stop collecting?
By comparing the arguments gathered so far against the original function's `length`, which is its declared parameter count. Once there are enough, it calls the function; otherwise it returns another collector. That reliance on `length` is also the limitation worth mentioning: it does not count rest parameters or ones with defaults.
Is currying actually used in real code?
Partial application is common — pre-filling a configuration argument to produce a more specific function — even where full currying is not. Interviewers ask about it less for daily utility than because a correct implementation demonstrates closures and recursion at the same time.
What follow-up questions come after currying?
Supporting a placeholder so arguments can be supplied out of order, and handling variadic functions where `length` cannot tell you when to stop. Both are asked to see whether you understand why the basic version relies on the parameter count in the first place.

Related Challenges

Continue learning with these related challenges

View All
JavaScript

Promise.all Polyfill

Implement your own Promise.all polyfill to understand how concurrent Promise handling works under the hood.

JavaScript · Promises · Async/AwaitPratik Rai ·

JavaScript

Function.prototype.call Polyfill

Implement a Function.prototype.call polyfill from scratch. Master JavaScript this binding, execution context, primitive boxing, and safe property assignment techniques.

JavaScript · Functions · thisPratik Rai ·

JavaScript

Array.prototype.reduce Polyfill

Implement Array.prototype.reduce from scratch, including the tricky no-initial-value path and the empty-array error case.

JavaScript · Arrays · PolyfillsPratik Rai ·