Currying in JavaScript

Currying changes a function that takes several arguments into a chain of functions that each take one argument.

The shape of a curried function

calculate(a, b, c)
becomes
calculate(a)(b)(c)

The behavior can stay the same. Only the way arguments arrive changes.

A small example

// Regular function
function add(a, b) {
  return a + b;
}

add(2, 3); // 5

// Curried function
function curriedAdd(a) {
  return function (b) {
    return a + b;
  };
}

curriedAdd(2)(3); // 5
  1. curriedAdd(2) runs.

    It returns a new function and remembers a = 2.

  2. The returned function receives 3.

    It can still read a through a closure.

  3. The final expression returns 5.

    Each pair of parentheses is a separate function call.

The same function with arrow syntax

Arrow functions make the nested shape compact. Read the arrows as "return another function."

const add = a => b => a + b;

const addTen = add(10);

addTen(5);  // 15
addTen(20); // 30

Why this is useful: the first call configures a reusable function. Later calls only provide the value that changes.

Build reusable predicates

A curried comparison can capture a property name and expected value, then work directly as an array callback.

const hasValue = key => expected => item =>
  item[key] === expected;

const isActive = hasValue("status")("active");

const activeUsers = users.filter(isActive);

The final function has the callback shape that filter needs. The configuration has already been supplied.

A generic curry helper

This helper keeps collecting arguments until it has at least as many as the original function declares.

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }

    return (...nextArgs) =>
      curried.apply(this, [...args, ...nextArgs]);
  };
}

const sum3 = (a, b, c) => a + b + c;
const curriedSum = curry(sum3);

curriedSum(1)(2)(3); // 6
curriedSum(1, 2)(3); // 6

fn.length only counts parameters before the first default parameter, and it does not count a rest parameter. Production helpers need an explicit arity when those cases matter.

When currying earns its keep

  • You repeatedly reuse the same configuration.

    Capture the stable argument once and create a focused function for changing values.

  • You compose small functions.

    One-argument functions fit naturally into pipelines where each return value becomes the next input.

  • The call reads more clearly.

    hasRole("admin")(user) can express a configured test. If the nesting makes readers pause, a named factory function may be better.

Check your understanding

  1. What does the first call to multiply(4)(5) return if multiply = a => b => a * b?
    Show answer

    multiply(4) returns a function that remembers a = 4. The second call supplies b = 5.

  2. When does the generic curry helper call the original function?
    Show answer

    It calls the original function when it has collected at least as many arguments as fn.length reports.

  3. What JavaScript feature lets a returned function remember earlier arguments?
    Show answer

    A closure. The inner function keeps access to bindings from the scope where it was created.