Currying

On this page 7

Currying is the transformation of a function that takes several arguments into a sequence of functions that each take exactly one argument and return the next function in the chain. In functional programming, a curried version of add(a, b) is written add(a)(b): calling it with a returns a new function that waits for b.

The technique is named after the logician Haskell Curry, although the idea appeared earlier in work by Moses Schönfinkel and Gottlob Frege. Currying matters because it makes partial application natural, simplifies function composition and lets general functions be specialized into reusable building blocks.

How currying works

Currying relies on two features: first-class functions, which can be returned from other functions, and closures, which remember the arguments already supplied. Each step of a curried function captures one argument in its closure and returns a function for the next argument, until all arguments are present and the original body runs.

In type notation, a function of type (A, B) -> C becomes A -> (B -> C), usually written without parentheses as A -> B -> C because the arrow is right-associative. The two forms are mathematically equivalent: every multi-argument function has exactly one curried counterpart, and the reverse transformation is called uncurrying.

FormSignatureCall
Uncurriedvolume(l, w, h)volume(2, 3, 4)
Curriedvolume(l)(w)(h)volume(2)(3)(4)
Partially appliedbase = volume(2)(3)base(4), base(10)
One function in uncurried and curried form

Currying and partial application

Currying and partial application are closely related but not the same operation. Currying changes the shape of a function into a chain of unary functions; partial application fixes some arguments of a function and returns a function of the remaining ones. Currying makes partial application trivial, because supplying the first arguments of a curried function is partial application.

ConceptWhat it doesResult
CurryingTurns an n-argument function into n unary functionsA chain of functions
Partial applicationFixes some arguments nowA function of the remaining arguments
Function compositionFeeds one function’s output into anotherA new combined function
Higher-order functionTakes or returns functionsThe general category currying belongs to
ClosureCaptures variables from an enclosing scopeThe mechanism that stores supplied arguments
Currying compared with neighboring concepts

A common confusion is calling any function factory curried. A function such as makeLogger(level) that returns a logger is a higher-order function, but it is currying only if it is a systematic transformation of a single multi-argument function.

Currying across programming languages

Languages differ in whether currying is built in or added by libraries. In Haskell and the ML family, every function is curried by default, so a two-argument function is really a function returning a function. In mainstream multi-paradigm languages, currying is a library helper or a manual pattern.

LanguageDefault behaviorTypical approach
HaskellAll functions curriedNative syntax f x y
OCaml and F#Functions curried by defaultNative partial application
ScalaOptional multiple parameter listsdef f(a: Int)(b: Int)
JavaScript and TypeScriptNot curriedArrow chains or a curry helper
PythonNot curriedfunctools.partial or nested functions
Java and C#Not curriedNested lambdas returning functions
Support for currying in common languages

In a natively curried language, argument order is a design decision. Library authors place the most stable argument first and the data last, so that map toUpper can be passed around as a ready-made function. This data-last convention is also followed by functional utility libraries in JavaScript.

Practical uses of currying

Currying is most useful when one general function must be specialized many times with the same leading arguments. It removes repetitive wrapper functions and produces small units that fit into pipelines of pure functions.

  • Configuration first, data last: formatPrice(currency)(amount) creates one formatter per currency.
  • Point-free pipelines: pipe(filter(isActive), map(toName)) composes steps without naming the data.
  • Event handlers: handleChange(fieldName) returns a handler that already knows which field it updates.
  • Dependency injection: createService(db)(logger) supplies infrastructure once and returns a ready service.
  • Validation rules: minLength(8) returns a reusable predicate for passwords or names.

These patterns work best when the curried functions are pure and free of side effects. A partially applied function that captures mutable state can behave differently over time, which undermines referential transparency.

Costs and pitfalls

Currying has real costs in languages that are not designed for it. Each partial call allocates a closure, which is usually negligible but can matter in tight loops, and stack traces through generic curry helpers are harder to read during debugging.

ProblemCauseRemedy
Lost type informationGeneric helpers on variadic functionsWrite explicit curried signatures
Unexpected argument countDefault or rest parameters ignored by the helperAvoid currying variadic functions
Readability lossLong chains like f(a)(b)(c)(d)Curry only where specialization is needed
Wrong argument orderData placed first instead of lastOrder arguments from stable to variable
Performance overheadClosure allocation in hot pathsUse uncurried functions inside loops
Typical problems with currying and their remedies

A frequent bug in JavaScript appears when a curry helper reads fn.length, which does not count default or rest parameters, so the function fires too early or never. Being explicit about arity avoids this class of errors.

Put simply

Currying turns one big request into a series of small questions. Instead of saying “make me a coffee, large, with oat milk” all at once, you answer one question at a time: which drink, then which size, then which milk.

The useful part is that you can stop halfway. After choosing “large coffee”, you have a ready shortcut that only asks about milk. Currying lets programmers create such shortcuts from general functions without rewriting them.

In practice

A team builds a web shop in TypeScript that shows prices in euros, dollars and pounds. Originally every component calls formatPrice(amount, currency, locale), repeating the currency and locale in dozens of places.

The developers rewrite it as a curried function formatPrice(locale)(currency)(amount). At startup, the app creates const formatEur = formatPrice("de-DE")("EUR") once, and components simply call formatEur(19.9). The same pattern produces reusable validators such as minLength(8) for the signup form.

The result is less duplicated configuration, fewer mistakes from mixing up argument order and small functions that are easy to unit test. The team keeps uncurried versions only inside a performance-sensitive price calculation loop.