Skip to content
JavaScript js functional 4 min read

Currying & Partial Application

Currying and partial application are two closely related techniques for specializing functions — turning a general-purpose function into a more focused one by locking in some of its arguments ahead of time. Both lean on closures, both improve reusability, and both make code read more declaratively. They are frequently confused, so this page draws a sharp line between them and shows how to implement each in modern JavaScript.

Currying vs partial application

Currying transforms a function that takes N arguments into a chain of N functions, each taking exactly one argument. You call them one at a time, and the final call produces the result.

Partial application fixes some (one or more) of a function’s arguments up front, returning a new function that takes the remaining arguments — all at once. There is no requirement that you supply one argument per call.

AspectCurryingPartial application
Arity per callExactly one argumentAny number of arguments
Result of a callAnother function until all args givenA function expecting the rest
Shapef(a)(b)(c)f(a, b) then g(c)
Built-in supportNone (write a helper)Function.prototype.bind

Manual currying with closures

The simplest curry is hand-written: each function returns the next, capturing the prior argument in its closure.

const add = (a) => (b) => (c) => a + b + c;

console.log(add(1)(2)(3)); // fully applied
const add10 = add(10);     // a fixed at 10
console.log(add10(2)(3));  // reuse the partially-built function

Output:

6
15

Each arrow returns a function until the last one, which has all three values in scope and can finally compute.

A generic curry helper

Writing nested arrows by hand is tedious for real functions. A reusable curry accepts a normal multi-argument function and returns a curried version that collects arguments until it has enough, then invokes the original.

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

const volume = (l, w, h) => l * w * h;
const curriedVolume = curry(volume);

console.log(curriedVolume(2)(3)(4)); // one at a time
console.log(curriedVolume(2, 3)(4)); // mixed grouping
console.log(curriedVolume(2, 3, 4)); // all at once

Output:

24
24
24

The helper uses fn.length (the function’s declared arity) to know when enough arguments have arrived. A flexible curry like this also tolerates supplying several arguments per call, blurring the line toward partial application.

Note: fn.length does not count rest parameters or parameters with default values. For variadic functions, prefer explicit partial application over a generic curry.

Partial application with bind

Function.prototype.bind is the native, zero-dependency way to partially apply. Its first argument sets this; every argument after that is pre-filled from the left.

function request(method, url, body) {
  return `${method} ${url} ${JSON.stringify(body)}`;
}

const post = request.bind(null, "POST");
const postToUsers = request.bind(null, "POST", "/api/users");

console.log(post("/api/logs", { level: "info" }));
console.log(postToUsers({ name: "Ada" }));

Output:

POST /api/logs {"level":"info"}
POST /api/users {"name":"Ada"}

A reusable partial helper

When you want clearer intent than bind(null, ...), a tiny partial helper reads better and skips the this argument entirely.

const partial = (fn, ...fixed) => (...rest) => fn(...fixed, ...rest);

const greet = (greeting, name) => `${greeting}, ${name}!`;
const sayHi = partial(greet, "Hi");

console.log(sayHi("Grace"));
console.log(sayHi("Linus"));

Output:

Hi, Grace!
Hi, Linus!

Practical uses

These techniques shine when you build small, configured functions once and reuse them across a codebase:

  • Event handlers: partial(handleClick, itemId) produces a handler bound to a specific item without an inline arrow on every render.
  • Pipelines: Curried, single-argument functions slot directly into compose/pipe, since each stage takes exactly one value.
  • Configuration: Lock in a logger level, an API base URL, or a currency formatter once, then call the specialized version everywhere.
const formatCurrency = (locale, currency) => (amount) =>
  new Intl.NumberFormat(locale, { style: "currency", currency }).format(amount);

const usd = formatCurrency("en-US", "USD");
const eur = formatCurrency("de-DE", "EUR");

console.log(usd(1999.5));
console.log(eur(1999.5));

Output:

$1,999.50
1.999,50 €

Best Practices

  • Reach for partial application via bind for everyday specialization — it is built in, fast, and obvious.
  • Use a generic curry helper mainly for fixed-arity, single-purpose functions destined for composition pipelines.
  • Order parameters most-stable first, data last so the value you vary most is supplied last (the “data-last” convention used by libraries like Ramda).
  • Avoid currying variadic functions or those with default parameters; fn.length will mislead the helper.
  • Give specialized functions descriptive names (postToUsers, usd) so call sites stay readable.
  • Do not over-curry: deeply nested single-argument chains can hurt clarity more than they help.
Last updated June 1, 2026
Was this helpful?