Implement curry in JavaScript

Currying converts f(a, b, c) into f(a)(b)(c), collecting arguments across calls until enough have arrived. Compare the number collected so far against func.length, the original function's declared arity. Once it’s satisfied, invoke; otherwise return a function that keeps collecting.

Easy closures partial application higher-order functions

The question

Usually asked as "write curry so that curry(add)(1)(2)(3) works". The follow-up is almost always one of two variants:

  1. Handle multiple arguments per call, so curried(1, 2)(3) also works.
  2. Handle empty calls, so curried(1)()(2) changes nothing.

The solution

A named function expression is what makes the recursion possible: curried has to be able to call itself with the accumulated arguments.

function curry(func) {
  return function curried(...args) {
    // Enough arguments collected: run the original.
    if (args.length >= func.length) {
      return func.apply(this, args);
    }
    // Otherwise keep collecting. An empty call adds nothing and changes nothing.
    return function (...next) {
      return curried.apply(this, args.concat(next));
    };
  };
}

How it works

func.length is the target. It reports the parameters declared before any default or rest parameter, which is exactly the arity you’re collecting towards. Each call concatenates rather than replaces, so arguments pile up across however many calls it takes, and an empty call adds nothing and fails the length check again. That’s why curried()(2) works without a special case for it.

The tests it has to pass

This is the part most explanations leave out. "It works" is not a claim you can check by reading. These are the 2 cases Practice Pad runs against your solution, and the implementation above passes all of them.

Easy2 cases · argument collection

collects one argument per call until the function is satisfied

function multiply(a, b, c) {
  return a * b * c;
}
expectEqual(curry(multiply)(2)(3)(4), 24);

treats empty calls as changing nothing

function multiply(a, b, c) {
  return a * b * c;
}
expectEqual(curry(multiply)()(2)(3)()(4), 24);

Edge cases to watch out for

Using a fixed arity instead of func.length

Hard-coding the number of arguments makes the helper work for exactly one function. Reading func.length is what makes curry general, and interviewers watch for it specifically.

Assuming exactly one argument per call

A curry that only accepts (a)(b)(c) breaks on (a, b)(c). Using rest parameters and concatenating handles both shapes with no extra code.

Using > instead of >= in the arity check

An off-by-one that means the function is never invoked, because the collected count reaches the arity exactly and then stops.

Forgetting that rest and default parameters don’t count

function f(a, b = 1) {} has length === 1, and function g(...args) {} has length === 0. Currying either one behaves surprisingly, which is a good thing to mention before being asked.

Follow up questions

How do you curry a function with a variable number of arguments?

You can’t infer the arity, so the caller has to signal completion. Either take the arity explicitly, as curry(fn, 3), or treat an empty call as "done" and invoke on curried(). A rest-parameter function reports length === 0, so any curry built on func.length invokes it immediately.

What’s the difference between currying and partial application?

Partial application fixes some arguments and returns a function taking the rest, once. Currying goes further and returns a chain that keeps accepting arguments one group at a time until the arity is satisfied. Every curried function does partial application; not every partial application is curried.

How does func.length work with default parameters?

It counts the parameters before the first one with a default, and rest parameters are never counted. So function f(a, b = 1, ...rest) {} reports a length of 1. Any curry built on func.length inherits that behaviour.

Is currying actually used in production code?

Rarely in the explicit f(a)(b)(c) form, but the underlying idea is everywhere: any function returning a preconfigured function is doing the same thing. Ramda and lodash/fp are built around it, and interviewers ask about it because it tests closures and higher-order functions at once.

Practise this with the tests running

Practice Pad runs these 2 cases against your own solution, in a real editor, on your Mac. Twenty-four questions, and an optional AI panel that explains why a test failed.

Download for macOS Free. Requires macOS 12 or later on Apple Silicon.

See all 24 questions