ESC

Type to search the knowledge base.

Currying and Partial Application

Curry vs partial application — unary chains, bind, practical helpers, and when extra abstraction hurts readability.

intermediate3 min read
  • javascript
  • currying
  • partial-application
  • functions

People conflate currying and partial application. Both produce functions with fewer free arguments; only one systematically turns f(a,b,c) into f(a)(b)(c). Interviews like the distinction; production code likes the smaller idea: fix some args, reuse the rest.

Partial application

Fix the first arguments; call later with the rest.

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

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

// manual
function partial(fn, ...fixed) {
  return (...rest) => fn(...fixed, ...rest);
}

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

bind is the language-built partial for this + leading args.

Currying

const curry2 = (fn) => (a) => (b) => fn(a, b);
const curry3 = (fn) => (a) => (b) => (c) => fn(a, b, c);

const volume = (l, w, h) => l * w * h;
const curried = curry3(volume);

curried(2)(3)(4); // 24
const areaWithHeight4 = curried(2)(3);
areaWithHeight4(4); // 24

A flexible curry accumulates until arity is met:

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

function sum3(a, b, c) {
  return a + b + c;
}

const cSum = curry(sum3);
cSum(1)(2)(3); // 6
cSum(1, 2)(3); // 6
cSum(1)(2, 3); // 6

Note: fn.length breaks with default params and rest params — real libraries handle that carefully.

Why teams use it

const map = curry((fn, arr) => arr.map(fn));
const doubleAll = map((x) => x * 2);

doubleAll([1, 2, 3]); // [2, 4, 6]

// point-free pipelines
const prices = [10, 20, 30];
const withTax = map((p) => p * 1.1);
withTax(prices);

Curried map/filter lets you build specialized helpers without nested lambdas everywhere — until it doesn’t, and names get cryptic.

Currying ≠ partial

Currying Partial
Shape Sequence of unary (or staged) calls One function with some args fixed
Arity Transforms multi-arg into chain Reduces arity once
Example f(a)(b)(c) g = f.bind(null, a) then g(b,c)

You can implement partial using curry (curried(a) returns a partial). They are related, not identical.

When to skip it

// clearer
function fetchUser(id, { signal } = {}) {
  return fetch(`/api/users/${id}`, { signal }).then((r) => r.json());
}

// unnecessary curry theater
const fetchUser = curry((id, opts) => /* ... */);

Don’t curry every helper. Do partially apply when the same config repeats (API base URL, logger prefix, i18n t bound to a namespace).

Interview answer

“Partial application fixes some arguments and returns a function for the rest — Function.prototype.bind is the built-in. Currying converts a multi-arg function into a chain of functions, each taking the next argument. I use partials for repeated config; I avoid heavy curry in app code when a named closure is clearer.”

Placeholders and practical API design

Full placeholder curry (map(_, arr)) needs a library. Most app code only needs partial application of leading config:

const request = (base) => (path, init) =>
  fetch(base + path, init).then((r) => r.json());

const api = request('https://api.example.com');
await api('/users');
await api('/posts', { method: 'POST', body });

That’s a closure factory — same power as curry, clearer call sites. Reach for multi-arity curry when building a shared FP toolkit; reach for factories when building product APIs teammates will call daily.

Further reading

Related guides