jsguides

Currying and Partial Application

Currying and partial application are two related techniques that change how you think about function arguments. Instead of passing all arguments at once, you break a function’s arity into smaller, reusable pieces. If you’ve ever written something like const doubleAll = arr => arr.map(x => x * 2), you’ve already glimpsed why this matters: you’re separating the transformation from the data it operates on.

Closures: the mechanism behind currying

Currying relies on closures. A closure is when a function retains access to variables from its outer scope even after that outer function has returned. Without closures, currying wouldn’t work.

function outer(a) {
  return function inner(b) {
    return a + b  // 'a' is captured from outer's scope
  }
}
const fn = outer(5)
fn(3)  // 8 — 'a' (5) is still accessible inside 'inner'

Every time you call a curried function, it returns a new function that closes over the arguments you’ve already provided. This is what allows the chained f(a)(b)(c) pattern to work.

What is Currying?

Currying transforms a function that takes multiple arguments into a sequence of functions, each taking exactly one argument. Named after mathematician Haskell Curry, it forces a strict unary chain.

// Regular function: all args at once
function add(a, b, c) {
  return a + b + c
}
add(1, 2, 3)  // 6

// Curried version: one arg per call
const curriedAdd = a => b => c => a + b + c
curriedAdd(1)(2)(3)  // 6

Think of it like ordering a pizza one topping at a time. You place the base, then cheese, then pepperoni; each step building on the last. You can’t skip ahead, but you also can’t mess up the order.

Writing a curry() Helper

You don’t need a library to curry functions. Here’s a working implementation:

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

The key is fn.length, which tells you how many parameters a function expects. When you’ve collected enough arguments, the original function runs. Until then, the curried function keeps accumulating arguments. This curry helper also accepts multiple arguments at once, so you are not forced into pure unary calls unless you want them.

function multiply(a, b, c) {
  return a * b * c
}
const curriedMultiply = curry(multiply)

curriedMultiply(2)(3)(4)    // 24
curriedMultiply(2, 3)(4)   // 24 — mixed style works too
curriedMultiply(2)(3, 4)   // 24

The mixed-call flexibility matters in practice. You can supply the arguments you have now and defer the rest without wrapping every call in parentheses.

The curry helper is useful for adapting existing functions, but arrow functions let you write curried functions directly with no wrapper at all.

const add = a => b => c => a + b + c
const multiply = a => b => c => a * b * c

add(1)(2)(3)       // 6
multiply(2)(3)(4)  // 24

This compact syntax is common in functional codebases. Libraries like Ramda use this style throughout their APIs. You lose named parameters, but you gain the ability to specialize functions on the left side of a composition. Arrow currying works because each arrow returns a new function that captures the outer parameter, which is the closure pattern from earlier applied at scale.

Arrow currying produces the tightest syntax, but bind offers a different tool: partial application. Where currying always takes one argument at a time in a fixed order, partial application lets you freeze any subset of arguments in any position.

Partial application fixes some arguments of a function to produce a new function with fewer parameters. It’s different from currying — you can fix any subset of arguments at any time, not just the first one.

function greet(greeting, name) {
  return `${greeting}, ${name}!`
}

const sayHello = greet.bind(null, 'Hello')
sayHello('Alice')   // "Hello, Alice!"
sayHello('Bob')     // "Hello, Bob!"

bind returns a new function with the first argument set to the context (ignored here since greet doesn’t use this) and any additional arguments pre-filled. When you pre-fill every argument, as shown next, the resulting function takes no parameters at all. Calling it just runs the original with the stored values:

const greetAlice = greet.bind(null, 'Hello', 'Alice')
greetAlice()  // "Hello, Alice!"

This is partial application taken to its logical endpoint. No arguments remain open, and the function becomes a simple thunk. In modern code, undefined is preferred over null for the context since we’re not using this in pure functions.

For more control over which arguments get pre-filled, Lodash provides curry and curryRight with placeholder support that goes beyond what bind can do.

import { curry, curryRight } from 'lodash'

const divide = (a, b) => a / b
const curriedDivide = curry(divide)

curriedDivide(10)(2)   // 5
curriedDivide(10, 2)   // 5 — also works

// curryRight fills arguments from the right
const curriedDivideRight = curryRight(divide)
curriedDivideRight(2)(10)  // 5

Lodash also supports placeholders (_) when you need to partially apply an argument in the middle. The placeholder tells Lodash to skip that position during the current call and fill it later. This gives you control over which argument gets deferred, rather than being locked into a left-to-right order:

const greet = (greeting, name, punct) => `${greeting}, ${name}${punct}`
const partial = curry(greet)
partial('Hello', _, '!')('Alice')  // "Hello, Alice!"

So far we have looked at the mechanics. The place where these patterns earn their keep is in everyday code: building configurable API clients, wiring event handlers, and chaining data transformations. The following examples all follow the same rhythm — provide configuration early, data late.

Practical patterns

So far we have looked at the mechanics. The place where these patterns earn their keep is in everyday code: building configurable API clients, wiring event handlers, and chaining data transformations. The following examples all follow the same rhythm — provide configuration early, data late.

API client factories

Currying shines when building layers of configuration:

const createClient = baseUrl => endpoint => headers => {
  return fetch(`${baseUrl}/${endpoint}`, { headers })
}

const githubClient = createClient('https://api.github.com')
const fetchRepos = githubClient('repos')
const withAuth = headers => fetchRepos(headers)

// Later, with different auth:
fetchRepos({ authorization: 'token abc' })

Each call in the chain narrows the scope. You establish the base URL once, then create specialized endpoint functions, and finally provide headers per request. The factory shape means you can create multiple clients for different APIs without repeating the setup logic.

Curried functions also simplify DOM event wiring. Instead of writing an inline closure for every listener, you can pre-fill a handler with the data that stays constant and let the event object arrive as the last argument.

Attaching handlers with pre-filled data is straightforward:

const handleClick = buttonId => event => {
  console.log(`Button ${buttonId} clicked at`, event.clientX, event.clientY)
}

document.querySelectorAll('.btn').forEach(btn => {
  btn.addEventListener('click', handleClick(btn.id))
})

handleClick(btn.id) returns a function that closes over btn.id. When the click fires, the handler has access to the correct button ID without any extra closure wiring on your part. The curried shape separates the “which button” concern from the “what happened” concern, and that separation keeps event setup tidy even as the number of handlers grows.

When you push this idea further, the same pattern applies to array transformations. Curried map and filter take their callback first and the array second, which makes them composable through a pipe.

Pair curried map and filter with a pipe function for expressive data processing:

const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)

const double = x => x * 2
const isEven = x => x % 2 === 0

const doubleEvens = pipe(
  arr => arr.filter(isEven),
  arr => arr.map(double)
)
doubleEvens([1, 2, 3, 4])  // [4, 8]

The curried style means filter and map take their function argument first and their array second. This ordering is what makes point-free composition possible, since each step is a function waiting for data.

Currying vs partial application

The terms get mixed up, but there’s a real distinction:

AspectCurryingPartial Application
OutputAlways unary chainFixes any number of args
ArgumentsOne at a timeAny number at a time
Patternf(a)(b)(c)f(a, b)(c)
Library support_.curry, R.curry_.partial, R.flip, bind
// Currying: unary only
const add = a => b => c => a + b + c
add(1)(2)(3)  // 6

// Partial application: fix any subset
const add = (a, b, c) => a + b + c
const fixFirst = a => (b, c) => add(a, b, c)
fixFirst(1)(2, 3)  // 6

Currying is a specific transformation with a fixed arity of one. Partial application is more general; you decide how many arguments to fix upfront.

Common Pitfalls

Performance in hot paths. Every curried call allocates a new function object. In tight loops, this garbage collection pressure adds up. Profile before committing to curried patterns in performance-critical code.

// Allocation in a loop — watch out
const curriedAdd = a => b => a + b
for (let i = 0; i < 100000; i++) {
  process(curriedAdd(1)(i))  // Creates 100k function objects per iteration
}

Readability for unfamiliar audiences. The a => b => a + b style reads differently from what most JavaScript developers expect. In a mixed-ability team, clarity often beats elegance.

Harder debugging. Each curried layer adds a frame to stack traces. When something goes wrong in a composed chain of curried functions, the error messages can be opaque. Name your intermediate steps if you need to debug.

Use currying where it reads naturally

Currying is most helpful when a function naturally becomes more specific one argument at a time. Data transforms, config builders, and reusable handlers often fit that shape. If your team has to pause and count parentheses at the call site, the style may be getting in the way. The pattern should make the flow of values easier to follow, not harder. When in doubt, compare the curried version with a direct function call and keep the one that is easier to explain.

Keep partial application practical

Partial application is often the more useful tool in day-to-day JavaScript because it lets you pre-fill the values that rarely change while leaving the rest open. That works well for labels, URLs, and callbacks that need a little setup before they are passed around. The goal is not to write every function in a chained style. The goal is to remove repetition and keep the reusable pieces small enough that they still feel like ordinary JavaScript.

Conclusion

Currying and partial application are tools for reshaping functions. Currying forces a unary chain; each call takes exactly one argument until the original function runs. Partial application is more flexible: fix whatever arguments you want upfront, pass the rest later.

Both patterns shine when you need reusable, specialized functions. Event handlers, API clients, and data pipelines all benefit. The real win is composition: when small functions snap together cleanly, the overall structure reads like the problem domain rather than the implementation details.

Closures are the mechanism that makes currying possible. Function composition chains curried pieces together. Scope determines what each returned function can access. Understanding these underlying mechanics gives you the confidence to use these patterns without surprises.

Pick the technique that matches the call shape

Currying is most helpful when you want a strict sequence of single-argument calls. Partial application is better when you only want to prefill a few values and leave the rest flexible. That distinction matters because it affects how the function reads at the call site. If a function will often be specialized in one or two places, partial application usually feels more natural.

Keep the helper obvious

The best curry or partial helper is the one your team can explain without a long pause. If the helper starts to hide control flow, it becomes harder to debug than the original function. Use the pattern to reduce repetition and improve composition, not to make the code feel clever for its own sake.

See Also