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.
| Form | Signature | Call |
|---|---|---|
| Uncurried | volume(l, w, h) | volume(2, 3, 4) |
| Curried | volume(l)(w)(h) | volume(2)(3)(4) |
| Partially applied | base = volume(2)(3) | base(4), base(10) |
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.
| Concept | What it does | Result |
|---|---|---|
| Currying | Turns an n-argument function into n unary functions | A chain of functions |
| Partial application | Fixes some arguments now | A function of the remaining arguments |
| Function composition | Feeds one function’s output into another | A new combined function |
| Higher-order function | Takes or returns functions | The general category currying belongs to |
| Closure | Captures variables from an enclosing scope | The mechanism that stores supplied arguments |
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.
| Language | Default behavior | Typical approach |
|---|---|---|
| Haskell | All functions curried | Native syntax f x y |
| OCaml and F# | Functions curried by default | Native partial application |
| Scala | Optional multiple parameter lists | def f(a: Int)(b: Int) |
| JavaScript and TypeScript | Not curried | Arrow chains or a curry helper |
| Python | Not curried | functools.partial or nested functions |
| Java and C# | Not curried | Nested lambdas returning functions |
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.
| Problem | Cause | Remedy |
|---|---|---|
| Lost type information | Generic helpers on variadic functions | Write explicit curried signatures |
| Unexpected argument count | Default or rest parameters ignored by the helper | Avoid currying variadic functions |
| Readability loss | Long chains like f(a)(b)(c)(d) | Curry only where specialization is needed |
| Wrong argument order | Data placed first instead of last | Order arguments from stable to variable |
| Performance overhead | Closure allocation in hot paths | Use uncurried functions inside loops |
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.