JavaScriptCurrying

Currying and Partial Application

Currying transforms a function that takes many arguments at once into a chain of functions that each take a single argument. Partial application is the related trick of fixing some arguments now and supplying the rest later. Both rely on closures, and both make tiny, reusable specialised functions out of generic ones.

The two ideas, side by side

JS
// Regular function — three args at once.
const add3 = (a, b, c) => a + b + c;
add3(1, 2, 3);              // 6

// Curried version — one arg at a time.
const add3c = a => b => c => a + b + c;
add3c(1)(2)(3);             // 6

// Partial application — fix some args, return a new function.
const addOne = add3.bind(null, 1);
addOne(2, 3);               // 6

Currying is a shape — a chain of single-argument functions. Partial application is an action — turning a function of N arguments into one of fewer. Curried functions are naturally partial-applicable.

Why this is useful
  • Configuration before use — write a generic helper, lock in the config, hand the rest to callers.

  • Cleaner pipelinespipe(filter(isAdult), map(getName)) reads better than re-wrapping callbacks each time.

  • Reuse without inheritance — partials create specialised functions without classes.

A real example — logging

JS
const log = level => tag => message =>
  console.log("[" + level + "] " + tag + ": " + message);

const info  = log("INFO");
const error = log("ERROR");

const httpInfo  = info("HTTP");
const dbError   = error("DB");

httpInfo("200 OK");
dbError("connection refused");
[INFO] HTTP: 200 OK
[ERROR] DB: connection refused
Manual currying

Writing the chain by hand is fine for two or three arguments. For more, write a helper.

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

const sum = (a, b, c) => a + b + c;
const cs  = curry(sum);

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

The helper collects arguments across calls. Once it has enough — fn.length is the function's declared arity — it invokes the original.

Partial application with bind

Function.prototype.bind was built for this. The first argument is this; the rest pre-fill the first arguments.

JS
function fetchJson(method, url, body) {
  return fetch(url, { method, body: body && JSON.stringify(body) })
    .then(r => r.json());
}

const get  = fetchJson.bind(null, "GET");
const post = fetchJson.bind(null, "POST");

get("/api/me");
post("/api/login", { user: "ada" });
bind vs arrow
`bind` is concise for fixing leading arguments. For non-leading ones, just write an arrow function: `const a3 = (...args) => fn("fixed", ...args)`.
Argument order matters

Partial application is easier when the parameters most likely to be fixed come first. Compare:

JS
// Awkward — the data is what changes most, but it is first.
const map = (arr, fn) => arr.map(fn);

// Friendlier for partial application — fn first, then data.
const mapC = fn => arr => arr.map(fn);
const upperAll = mapC(s => s.toUpperCase());

upperAll(["hi", "there"]);   // ["HI", "THERE"]

Libraries like Ramda put the data parameter last (called "data-last") for exactly this reason — every helper is naturally curry-friendly.

A peek at Ramda

Ramda is a functional library where every multi-arg function is auto-curried. You almost never call curry yourself.

Ramda style — illustrative

JS
// import * as R from "ramda";

// const filterAdults = R.filter(p => p.age >= 18);
// const getNames     = R.map(p => p.name);
// const adultNames   = R.pipe(filterAdults, getNames);

// adultNames(people);

The same pattern works with hand-rolled curry and pipe helpers — the library just provides them pre-built, along with hundreds of small data-last functions.

When to leave it alone
  • For a function called once, currying is noise. add(1, 2, 3) beats add(1)(2)(3).

  • Over-curried APIs make stack traces harder to read — each call has its own frame.

  • TypeScript types for deeply curried functions can get baroque.

The mental shift
Once you see functions as values, currying stops feeling exotic. It is just another way to write `bind`. The payoff is a library of small reusable specialised functions that compose with no glue code.