Module 8 · Functional & Advanced Patterns ⏱ 18 min

Currying & Partial Application

By the end of this lesson you will be able to:
  • Translate a multi-argument function into a curried chain of single-argument functions
  • Use closures to pre-fill arguments and create specialized functions
  • Decide when currying or partial application actually helps versus adding noise

You already write functions that take several arguments at once: multiply(3, 4). That is fine when you have both numbers in hand. But suppose the 3 is a fixed tax rate you reuse across the whole program, and only the price changes from call to call. You end up repeating multiply(0.07, price) everywhere, and if the rate ever changes you must find every copy.

Currying offers a different shape. Instead of taking all its arguments at once, a curried function takes one argument and returns a new function waiting for the next. Call it once with the rate and you get back a dedicated addTax function; hand that a price and it returns the total. One general function becomes many specialised ones, each pre-loaded with the arguments you already know. The name honours the logician Haskell Curry, though the idea predates him — what matters is the shape, not the history.

flowchart LR
  A["add(a, b, c)"] --> C["curry"]
  C --> B["add(a)(b)(c)"]
  B --> S["one argument per step"]
  style C fill:#e0900b,color:#fff
  style B fill:#3776ab,color:#fff
Currying reshapes one multi-argument function into a chain of functions that each accept a single argument.

How currying works

A curried function is just a chain of nested closures. The outer function captures its argument in a closure and returns an inner function; the inner captures the next argument and returns another, until the final function has everything it needs and returns the result. Each step along the way is a perfectly good function you can store, pass around, or call later.

The notation gets compact with arrows: (a) => (b) => a + b reads as a function that takes a and returns a function that takes b and returns a + b. Nothing here is exotic — it is the closures from the previous module, used to delay a computation until its last argument arrives.

A two-argument function, rewritten in curried form. Press Run.
function multiply(a, b) {
  return a * b;
}

// The same logic, curried: one argument per step
function curriedMultiply(a) {
  return function (b) {
    return a * b;
  };
}

console.log(multiply(3, 4));            // 12

const triple = curriedMultiply(3);      // fix the first argument
console.log(typeof triple);             // function
console.log(triple(4));                 // 12
console.log(triple(10));                // 30

Tracing a curried call

Read curriedMultiply(3)(4) right to left as two separate calls. The first, curriedMultiply(3), captures 3 in a closure and hands back a function that is waiting for a second argument. Store it in triple, and triple is now a self-contained function — it carries the 3 with it wherever it goes. The second call, (4), feeds 4 to that stored function, which multiplies its captured 3 by 4 and returns 12.

Currying versus partial application

The two terms are related but not identical. Currying transforms a function so it accepts arguments one at a time, fully. Partial application is the broader idea: fix some arguments now, get back a function for the rest, however many remain. Every curried call is a partial application, but you can partially apply a normal function without currying it at all.

JavaScript gives you a built-in tool for this: fn.bind(null, fixedArg) returns a copy of fn with the first arguments already supplied. You can also do it by hand with a closure. Either way, you are pre-filling the arguments you already know and leaving the rest open. Think of currying as a full conversion of a function's shape, and partial application as a convenient fix-up you can apply to any function.

flowchart LR
  F["add(a, b)"] --> P["fix a = 10"]
  P --> G["add10(b) = 10 + b"]
  G --> R["add10(5) = 15"]
  style P fill:#b45309,color:#fff
  style G fill:#1e7f3d,color:#fff
Partial application fixes one argument now and returns a smaller function that waits for the rest.
Partial application by hand with a closure, and the built-in bind shortcut.
function greet(greeting, name) {
  return `${greeting}, ${name}!`;
}

// By hand: pre-fill `greeting` with a closure
function sayHi(name) {
  return greet("Hi", name);
}

// With bind: fix the first argument in one call
const sayYo = greet.bind(null, "Yo");

console.log(sayHi("Ada"));     // Hi, Ada!
console.log(sayYo("Linus"));   // Yo, Linus!

When it helps (and when it does not)

Currying earns its keep when one argument is stable and another varies — a logger with a fixed level, a fetcher with a fixed base URL, a formatter with a fixed locale. Pre-fill the stable piece and pass the specialised function around instead of repeating the constant. It also glues neatly into map and filter, which expect single-argument callbacks; mapping triple over an array reads as nums.map(triple) — no anonymous callback, no repeated 3, just the specialised function doing its job.

The trap is overuse. Do not curry a function you only ever call with every argument at once — the extra calls add ceremony and confusion for no benefit. Reach for currying when it removes repetition; leave it alone when a plain add(a, b) already says everything.

flowchart LR
  X["10"] --> A["add10"]
  A --> Y["20"]
  Y --> B["double"]
  B --> Z["40"]
  style A fill:#3776ab,color:#fff
  style B fill:#e0900b,color:#fff
Specialised functions become small building blocks you can chain into a pipeline.
Build specialised helpers from one curried function, then reuse them.
const multiply = (a) => (b) => a * b;
const double = multiply(2);
const triple = multiply(3);

const nums = [1, 2, 3];
console.log(nums.map(double));                 // [2,4,6]
console.log(nums.map(triple));                 // [3,6,9]
console.log(nums.map((n) => double(triple(n))));   // [6,12,18]
Exercise

Write a curried function multiply(a) that returns a new function. That returned function takes b and returns a * b. So multiply(3)(4) is 12.

function multiply(a) {
  // return a function of b that returns a * b
}
Exercise

What does this print? Each call to add takes one argument and returns a function until it has all three.

function add(a) {
  return function (b) {
    return function (c) {
      return a + b + c;
    };
  };
}
console.log(typeof add(1));
console.log(typeof add(1)(2));
console.log(add(1)(2)(3));
Exercise

Write curry2(fn) that turns any two-argument function into a curried one. So if add = (a, b) => a + b, then curry2(add)(2)(3) is 5, and curry2(add)(10) is a function still waiting for its second argument.

function curry2(fn) {
  // return a => b => fn(a, b)
}
Exercise

This greet is meant to be curried: greet("Hi")("Ada") should return "Hi, Ada!". But it returns the string too early — it is missing a level of functions. Fix it so the first call returns a function that is still waiting for the name.

function greet(greeting) {
  return `${greeting}, friend!`;   // returns too early — not curried
}
Exercise

Given const f = (a) => (b) => a + b, which call correctly produces the number 7?

Recap

  • Currying turns a multi-argument function into a chain that takes one argument at a time: f(a)(b)(c) instead of f(a, b, c).
  • It is built from nested closures — each step captures one argument and returns a function for the rest.
  • Partial application is the broader idea: fix some arguments now, leave the rest open. fn.bind(null, fixed) does this in one call.
  • Currying pays off when one argument is stable and another varies; it is noise when you always pass every argument anyway.

Next you'll learn memoization, which uses the same closure trick to remember a function's answers and skip repeat work.

Checkpoint quiz

What does a curried two-argument function return after you give it just its first argument?

How does fn.bind(null, x) relate to partial application?

Go deeper — technical resources