☕ Java

Currying

Currying is the technique of transforming a function that accepts multiple arguments into a chain of functions that each accept exactly one argument, so that calling the first function with the first argument returns a new function waiting for the second argument, and so on until every argument has been supplied and a final result is produced — a two-argument function becomes a one-argument function that, given the first argument, returns another one-argument function that, given the second, produces the result. This is distinct from partial application, which means fixing some subset of a function's arguments to produce a new function over the rest, since partial application does not require restructuring a function into a one-argument-at-a-time chain and can be done directly on an ordinary multi-argument function. Languages such as Haskell curry every function by default, with no special syntax needed to call a curried function one argument at a time; Java has no built-in support for currying, so a curried function must be constructed by hand as a chain of nested functional interfaces, most often Function<A, Function<B, R>> for two arguments, nested one layer deeper for each additional argument. This entry covers the precise definition of currying and how it differs from partial application, how to build curried functions in Java using nested Function types and a reusable curry helper, and the practical situations where currying earns its verbosity in Java versus situations where a simpler approach reads better.

What Currying Is, and How It Differs from Partial Application

Currying restructures the type of a multi-argument function into a chain of single-argument functions: rather than a function of two arguments taken at once, currying produces a function that takes only the first argument and returns a new function that takes only the second and produces the final result. Supplying all the arguments still happens, but one at a time, through a sequence of calls, each one producing an intermediate function until the chain is exhausted and a concrete result comes out the other end. This generalizes to any number of arguments by nesting one layer deeper per parameter. Partial application is a related but distinct idea: it means fixing one or more of a function's arguments ahead of time, producing a new function that only needs the remaining arguments, without requiring that the original function be restructured into a strictly one-at-a-time chain first. A regular two-argument BiFunction can be partially applied directly — bind one argument to a fixed value and return a Function that calls the original BiFunction with that fixed value and whatever argument it's given — with no currying involved at all. Currying and partial application overlap in practice because calling a curried function with its first argument and stopping there is itself a form of partial application — the intermediate function that comes back is exactly a partially-applied version of the original function — but the two terms describe different things: currying is about a function's shape, one argument per step all the way through, while partial application is about fixing some arguments now and leaving the rest for later, by whatever shape is convenient.
Java
// ── The type-level transformation that currying performs ───────────────
// Uncurried: takes both arguments at once
BiFunction<Integer, Integer, Integer> addUncurried = (a, b) -> a + b;

// Curried: takes ONE argument, returns a function awaiting the next one
Function<Integer, Function<Integer, Integer>> addCurried =
        a -> b -> a + b;

int viaUncurried = addUncurried.apply(3, 4);              // 7, both args at once
int viaCurried    = addCurried.apply(3).apply(4);          // 7, one arg at a time

// ── Partial application WITHOUT currying — fixing one arg directly ─────
static Function<Integer, Integer> bindFirstArg(BiFunction<Integer, Integer, Integer> fn, int fixedA) {
    return b -> fn.apply(fixedA, b);    // no curried chain built — just one wrapper
}

Function<Integer, Integer> addThree = bindFirstArg(addUncurried, 3);
System.out.println(addThree.apply(4));   // 7 — partial application, no currying involved

// ── Calling a curried function partway IS itself partial application ───
Function<Integer, Integer> alsoAddThree = addCurried.apply(3);   // stop after 1 arg
System.out.println(alsoAddThree.apply(4));   // 7 — same result, reached via currying

Implementing Curried Functions in Java with Nested Functional Interfaces

Because Java has no syntax for currying, a curried function is just an ordinary lambda whose body is itself another lambda — the nesting in the code mirrors the nesting in the type. For two arguments, the type is Function<A, Function<B, R>>; for three, it's Function<A, Function<B, Function<C, R>>>, and so on, with one additional layer of Function wrapping for every extra parameter. Writing this out by hand for every function that needs currying is repetitive, so a small generic curry helper method is typically written once and reused: it accepts an existing BiFunction, or a custom three-argument interface since the JDK ships no TriFunction, and returns the equivalent curried Function chain, performing the lambda-nesting transformation in one place.
Java
// ── Manually writing a curried function ─────────────────────────────────
Function<Integer, Function<Integer, Integer>> multiplyCurried =
        a -> b -> a * b;

System.out.println(multiplyCurried.apply(6).apply(7));   // 42

// ── Generalizing to three arguments: one more layer of nesting ─────────
Function<Integer, Function<Integer, Function<Integer, Integer>>> volumeCurried =
        l -> w -> h -> l * w * h;

System.out.println(volumeCurried.apply(2).apply(3).apply(4));   // 24

// ── A reusable curry() helper for two-argument functions ───────────────
static <A, B, R> Function<A, Function<B, R>> curry(BiFunction<A, B, R> fn) {
    return a -> b -> fn.apply(a, b);
}

BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
Function<String, Function<Integer, String>> repeatCurried = curry(repeat);

System.out.println(repeatCurried.apply("ab").apply(3));   // "ababab"

// ── A TriFunction interface (not provided by the JDK) and its curry() ──
@FunctionalInterface
interface TriFunction<A, B, C, R> {
    R apply(A a, B b, C c);
}

static <A, B, C, R> Function<A, Function<B, Function<C, R>>> curry(TriFunction<A, B, C, R> fn) {
    return a -> b -> c -> fn.apply(a, b, c);
}

TriFunction<Integer, Integer, Integer, Integer> volume = (l, w, h) -> l * w * h;
Function<Integer, Function<Integer, Function<Integer, Integer>>> volumeCurried2 = curry(volume);
System.out.println(volumeCurried2.apply(2).apply(3).apply(4));   // 24

Practical Uses and the Verbosity Trade-off

Currying earns its place in Java code mainly where the goal is reusable, partially-configured behavior built up in stages and applied many times afterward: a discount calculator curried on the discount rate lets a single rate be fixed once per call site and the resulting Function reused across an entire batch of prices, a validator curried on its configuration can be specialized once and then handed to many calls of a generic validation utility, and pipeline-building APIs that hand back partially-applied stages for further configuration rely on exactly this shape. The benefit in each case is that the configuration step and the application step are cleanly separated in the type system itself — a Function that has already been told its discount rate cannot accidentally be called with the wrong rate later, because there's no rate parameter left to get wrong. The trade-off is verbosity, and it compounds with each added parameter. In a language with native currying, like Haskell, calling a function with both of its arguments already is, syntactically and silently, calling the curried function with one argument and then the result with the next; there's no visual or ceremonial difference between calling a curried function and calling an ordinary one. In Java, that chain is explicit: chaining two apply calls is unmistakably more to read and write than calling a single method with both arguments for the exact same computation, and a curried chain for three parameters is visually heavier still, with three layers of generic Function types to track in the type signature alone. For this reason, currying in Java is generally reserved for cases where the reusable, staged-configuration benefit specifically matters — building flexible library APIs, fluent configuration builders, or genuinely functional utility code — while everyday application code that simply needs to call a function with a few arguments at once is usually clearer written as a plain multi-parameter method, or with a single one-step partial-application helper, rather than going through a fully curried chain.
Java
// ── A curried discount calculator, configured once, reused many times ──
Function<Double, Function<Integer, Integer>> discountedPriceCurried =
        rate -> price -> (int) Math.round(price * (1 - rate));

Function<Integer, Integer> twentyPercentOff = discountedPriceCurried.apply(0.20);
Function<Integer, Integer> tenPercentOff    = discountedPriceCurried.apply(0.10);

List<Integer> prices = List.of(100, 250, 80, 60);
List<Integer> salePrices = prices.stream().map(twentyPercentOff).toList();
// twentyPercentOff is reused across every price with NO risk of the rate
// being mixed up between calls, because the rate is already baked in.

// ── The same logic, three ways — illustrating the verbosity trade-off ──

// (a) Plain method — clearest for a single, one-off call:
static int discountedPricePlain(int price, double rate) {
    return (int) Math.round(price * (1 - rate));
}
discountedPricePlain(100, 0.20);                       // simplest call

// (b) One-step partial application — good when the rate is fixed
//     once and reused, but full currying isn't otherwise needed:
static Function<Integer, Integer> withRate(double rate) {
    return price -> discountedPricePlain(price, rate);
}
Function<Integer, Integer> off20 = withRate(0.20);
off20.apply(100);

// (c) Full currying — most ceremony, but composes naturally with other
//     curried/higher-order-function pipeline code that expects this shape:
Function<Double, Function<Integer, Integer>> curried = rate -> price -> discountedPricePlain(price, rate);
curried.apply(0.20).apply(100);

Related Topics in Functional Programming

Functional Programming Basics
Functional programming is a programming paradigm that treats computation as the evaluation and composition of functions, favors expressions that produce values over statements that mutate state, and prefers passing behavior around as data rather than encoding it only inside class hierarchies. Java was designed from the outset as an object-oriented, imperative language: methods could be invoked but never treated as values — they could not be stored in a variable, passed as an argument, or returned from another method, except through the verbose workaround of wrapping a single method inside an anonymous class that implemented a one-method interface. Java 8 closed this gap by introducing lambda expressions, method references, and a standard library of single-method functional interfaces in java.util.function, together with the Stream API built on top of them, giving Java a genuinely functional layer on top of its existing object-oriented core without abandoning that core. This entry covers what distinguishes functional style from imperative and object-oriented style, how Java represents a function as an instance of a functional-interface type rather than as a true first-class value, the standard functional interfaces the JDK provides for common shapes of behavior, and how adopting functional style changes the shape of everyday Java code.
Pure Functions
A pure function is a function whose return value depends solely on the arguments passed to it, with no dependence on any hidden or external state, and which produces no observable effect on the world beyond computing and returning that value — no mutation of arguments, no writes to shared state, no I/O, and no dependence on anything that can change between calls such as the system clock or a random number generator. Calling a pure function with the same arguments always produces the same result, a property known as referential transparency, which means a call to a pure function can be replaced anywhere in a program by its result without changing what the program does. Java does not enforce purity at the language level — any method, lambda, or method reference may freely read and write shared mutable state, perform I/O, or behave non-deterministically — so purity in Java is a discipline the developer chooses to follow rather than a guarantee the compiler provides. This entry covers the precise definition of purity and referential transparency, the common ways Java code becomes impure even unintentionally, and the practical payoffs of writing pure functions: simpler testing, safe caching and memoization, and safe parallel execution.
Higher Order Functions
A higher-order function is a function that takes one or more other functions as parameters, returns a function as its result, or both — as opposed to a first-order function, which only takes and returns plain data values like numbers, strings, or objects. Java has supported a narrow form of this since its very first versions, since passing a Runnable to a Thread or a Comparator to Collections.sort is technically passing a function (wrapped in a single-method interface) into another method, but writing an anonymous class at every call site made the pattern feel like a special case reserved for a handful of built-in APIs rather than a general technique available for everyday code. Lambda expressions and method references, introduced in Java 8, made supplying a function as an argument concise enough that higher-order functions became a natural, everyday tool — most visibly through the Stream API's map, filter, and reduce, but equally usable in ordinary application code through java.util.function. This entry covers the definition of a higher-order function, how Java represents functions as arguments and return values without true first-class functions, the most common shapes of functions that take functions as parameters, and the complementary shape of functions that build and return other functions, including function composition.
Closures
A closure is a function bundled together with the variables from its enclosing lexical scope that it refers to, captured at the time the function is created, so that the function can keep using those variables even after the scope that originally held them has finished executing. In Java, both lambda expressions and anonymous inner classes form closures: they can refer to local variables and parameters of the method that creates them, and to the fields of the enclosing object, even after that method has returned and its stack frame is gone. Java places one significant restriction on this that languages like JavaScript do not: any local variable or parameter captured by a lambda or inner class must be effectively final — assigned exactly once and never reassigned — which rules out the kind of closure-over-a-mutable-counter pattern that is idiomatic in other languages, and requires a small workaround whenever genuinely mutable shared state is needed. This entry covers what a closure captures and how, why Java enforces the effectively-final rule, the standard workarounds for needing mutable captured state, and the practical patterns and memory implications of using closures in real code.