Home / Articles / JavaScript Currying Demystified: Closures, Partial Application, Reuse

This article is published in English.

JavaScript Currying Demystified: Closures, Partial Application, Reuse

Learn how currying turns multi-argument JavaScript functions into reusable single-argument chains, how it differs from partial application, and when to skip it.

1051 words

If you keep passing the same first argument to a function over and over, you pay a small repetition tax on every call. Currying lets you lock in part of a function's input once and reuse the result. Below you will see what currying is, how closures power it, how it differs from partial application, and when it genuinely improves code.

What currying means

Currying turns a function that takes several arguments into a sequence of functions that each take exactly one. A regular call supplies everything at once:

f(a, b, c)

The curried form supplies one value per call, and each call returns the next function in the chain:

f(a)(b)(c)

Each intermediate function remembers the values from earlier calls. That memory comes from closures, covered below.

Converting a plain function

Start with an ordinary two-argument addition:

function sum(a, b) {
  return a + b;
}

sum(2, 3); // 5

A small helper can wrap any two-argument function: the outer function takes the first value, returns a function that takes the second, and only then calls the original with both:

function curry(fn) {
  return function (a) {
    return function (b) {
      return fn(a, b);
    };
  };
}

const curriedSum = curry(sum);
curriedSum(2)(3); // 5

What to notice:

  • curry is higher-order: its input and its output are both functions.
  • Every function it hands back expects just one value.
  • The inner function can still read a after the outer call has returned, because it closes over it.

This helper only handles exactly two parameters. General-purpose versions check fn.length and keep collecting arguments until enough have arrived, but the idea is identical.

A practical case: specialized loggers

Arithmetic hides the payoff, so consider a logger that takes a severity level and a message:

function log(level, message) {
  console.log(`[${level}] ${message}`);
}

Call sites quickly start repeating the same level:

log("ERROR", "Something broke");
log("ERROR", "Invalid token");
log("ERROR", "API failed");

A curried logger lets you create pre-configured loggers once:

function log(level) {
  return function (message) {
    console.log(`[${level}] ${message}`);
  };
}

const errorLog = log("ERROR");
const infoLog = log("INFO");

errorLog("Something broke");
errorLog("Invalid token");
infoLog("User logged in");

The level is fixed when errorLog and infoLog are created. Call sites get shorter, intent reads naturally, and the specialized functions can be exported or injected elsewhere. That mix of reuse and expressiveness is the main reason to use currying.

Partial application

Partial application means pre-filling some arguments of an existing function to get a new function, without changing the original's shape. JavaScript supports it natively through bind:

function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2);
double(5); // 10

The first bind argument sets this, which multiply ignores, hence null. multiply still takes two arguments; double is just a variant with the first one supplied.

Currying versus partial application

The two share a goal but not a mechanism:

  • Currying restructures a function into a chain of single-argument functions.
  • Partial application fills in some arguments now and leaves the function's structure as it was.

Every step of a curried chain is effectively a one-argument partial application, but partial application never requires currying.

Open-ended currying

A playful variant keeps accepting values until you end the chain with an empty call:

let infiniteSum = function (a) {
  return function (b) {
    if (b !== undefined) return infiniteSum(a + b);
    return a;
  };
};

infiniteSum(1)(2)(3)(4)(); // 10

How it unfolds:

  • Each call with a value adds it to the running total and returns a new function carrying that total.
  • The final () leaves b as undefined, so the sum is returned.
  • Closures carry the total from one step to the next.

The explicit b !== undefined check matters. A truthiness test like if (b) would treat 0 as the terminator, so infiniteSum(1)(0)(2)() would stop at the zero instead of returning 3. This pattern is a great closure exercise and a common interview question, but the easy-to-forget final call makes it a poor default for production APIs.

Closures make it all work

Here is the smallest curried adder:

function add(a) {
  return function (b) {
    return a + b;
  };
}

add(3)(4); // 7

add(3) returns the inner function and finishes, yet that inner function can still read a. That is a closure: a function keeps access to variables from the scope where it was defined, even after the outer function has returned. Because the captured value persists, you can store and reuse the partially applied function:

const add3 = add(3);

add3(4); // 7
add3(10); // 13

For more on how those captured variables are resolved, see how the scope chain resolves variables.

When to use it and when to skip it

Currying pays off when:

  • the same leading arguments appear at many call sites
  • it makes code clearer rather than more cryptic
  • you want small, composable, reusable functions
  • you build utilities such as loggers, validators, formatters or API helpers, where a configuration value is known early

Skip it when:

  • a simple one-off function gains nothing from being split
  • your team is unfamiliar with the pattern
  • the chained calls hurt readability

Wrapping up

Currying is really about ordering a function's inputs by when they become known: configuration such as a log level or base URL gets fixed early, per-call data arrives later. Closures do the remembering, bind offers lighter partial application when you do not need a full chain, and the deciding question is always whether calling code becomes easier to read and reuse.