Back to JavaScript
JavaScript
easy
mid

What is the difference between currying and partial application in JavaScript?

Currying: transform an n-arg function into a chain of unary functions — `f(a, b, c)` becomes `f(a)(b)(c)`. Partial application: pre-fix some args, returning a function expecting the rest — `f.bind(null, a)`. Both rely on closures. Useful for point-free style, event handlers per item, and composition. Overused, they hurt readability.

4 min read·~10 min to think through

Two related but distinct ideas, both grounded in [[closures-and-lexical-scoping]].

Partial application

Bind some arguments now, supply the rest later:

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

const addFive = add.bind(null, 5);
addFive(2, 3);   // 10

// or with a helper:
const partial = (fn, ...pre) => (...rest) => fn(...pre, ...rest);
const greet = (greeting, name) => greeting + ", " + name;
const hi = partial(greet, "Hi");
hi("Sam");       // "Hi, Sam"

Currying

Transform a multi-arg function into a chain of unary calls:

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

const sum3 = curry((a, b, c) => a + b + c);
sum3(1)(2)(3);     // 6
sum3(1, 2)(3);     // 6
sum3(1)(2, 3);     // 6

Each call returns a function that captures the args supplied so far — pure closure magic.

Difference in one line

  • Partial: f(a, b, c)f(a)(b, c). Pre-fills some args once.
  • Curry: f(a, b, c)f(a)(b)(c). Transforms the signature systematically.

Why bother

  1. Reusable handlers:
jsx
function ItemList({ items, onSelect }) {
  return items.map((item) => (
    <button key={item.id} onClick={() => onSelect(item.id)}>{item.name}</button>
  ));
}
// vs.
const onSelectId = (id) => () => onSelect(id);    // curried form

(Note: the inline arrow is fine for small lists; for very large lists, the curried form lets you memoize per id and skip allocating a new function per render.)

  1. Composition / point-free style (Ramda, lodash/fp):
js
const isAdult = pipe(prop("age"), gte(18));
  1. Configuration of generic helpers:
js
const logger = (level) => (msg) => console.log(`[${level}]`, msg);
const error = logger("ERROR");
error("boom");

When it hurts

  • Stack traces get noisier (more anonymous frames).
  • Readability for newcomers — f(a)(b)(c) reads worse than f(a, b, c) unless the team is fluent.
  • Debugging — break-pointing inside curried chains is annoying.
  • Bundle size if you import all of ramda for one thing.

Use it where it pays — handlers, composition, fp pipelines — not as a default style.

Interview framing

"Partial application pre-fixes some arguments; currying transforms an n-arg function into a chain of unary calls. Both rely on closures to remember the supplied args. bind does partial application. A 10-line curry helper covers the rest. I use them for reusable handlers, pipe/compose pipelines, and configurable helpers — not as a default style, because debugging and readability take a hit when overused."

Follow-up questions

  • Implement curry from scratch.
  • How does Function.prototype.bind relate?
  • When would you prefer partial over curry?

Common mistakes

  • Mixing up the two terms.
  • Currying functions with variadic arity — fn.length lies for default/rest params.
  • Allocating new curried functions in every render.

Performance considerations

  • Each call in a curry chain allocates a closure. Trivial for handlers; measurable in tight loops. Memoize curried functions per stable key if rendering large lists.

Edge cases

  • Default parameters change fn.length.
  • Rest parameters make fn.length 0.
  • this binding — arrow vs regular.

Real-world examples

  • Ramda / lodash/fp pipelines, Redux compose / applyMiddleware, React event handlers per row.

Senior engineer discussion

Seniors know when to use it (composition, handlers) and when to avoid it (one-off code where it just obscures the call). They also point out that `Function.prototype.bind` is the original partial application primitive in JS.

Related questions