skip to content

Currying & Partial Application Idioms

Currying is expressed as functions returning functions, and partial application as a closure that captures some arguments early. Kotlin's pragmatic take is that default and named arguments cover most of the cases other languages need currying for.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

Why does idiomatic Kotlin usually prefer default and named arguments over currying or hand-rolled partial application? Give a concrete comparison.

level: middleimportance: must knowfreq 45%

answer

  1. Defaults + named args solve the common 'fix some args' case
  2. Resolved at the call site — can't be handed off partial
  3. No closures/allocations; full IDE + inline support
  4. Closures/currying win when you need a first-class function value
  5. Rule: defaults for calls, lambdas for passing functions around

basics

~20 s

Kotlin lets you give parameters default values and pass arguments by name, so you can omit or fix arguments at the call site without building chains of lambdas. It is clearer and avoids extra objects.

solid answer

~50 s

Currying and partial application exist to let callers fix some arguments and vary others. Kotlin solves the common case directly with **default arguments** (`fun f(a: Int, b: Int = 0)`) and **named arguments** (`f(b = 5)`), so you don't restructure the function or allocate closures. Compared to a curried `(Int) -> (Int) -> Int`, a function with defaults is read at the call site as a normal call, gets full IDE support, avoids per-stage lambda allocations, and works with `inline` functions for zero-overhead higher-order use. Currying still shines when you must *pass the partially-applied function as a value* into `map`/callbacks/strategy slots, or build point-free combinators. Default arguments cannot be "left partial and handed off"; they're resolved at the call. So the rule of thumb: defaults/named for fixing-at-call, lambdas/closures when you need a first-class function of the remaining arguments.

go deeper

for a junior

Knows default and named arguments exist and can fix or omit an argument at a call.

for a middle

Compares a defaulted call against a curried chain and explains why defaults are clearer and allocation-free.

for a senior

Articulates the precise boundary: defaults for call-site fixing, closures/currying when a first-class function of the remaining args is required.

for a principal

Establishes API-design guidance — preferring defaults/overloads for ergonomics and reserving functional encodings for genuine higher-order needs, accounting for inline and binary-compatibility implications of default arguments.

## The problem both solve Currying, partial application, and default arguments all answer: *"I want to fix some arguments and leave others flexible."* Kotlin's pragmatic answer is **default arguments** plus **named arguments**, not functional encodings. ## Default and named arguments A **default argument** gives a parameter a fallback value used when the caller omits it. A **named argument** lets the caller specify a parameter by name, in any order, skipping earlier optional ones. ```kotlin fun connect(host: String, port: Int = 443, useTls: Boolean = true): Connection { /* ... */ } connect("example.com") // port=443, useTls=true connect("example.com", port = 8080) // override one, by name connect("example.com", useTls = false) // skip port, name the next ``` No closures, no nested lambdas — just one clear call. ## Side-by-side with a curried/partial version ```kotlin // Functional encoding val connectCurried: (String) -> (Int) -> (Boolean) -> Connection = TODO() val c = connectCurried("example.com")(8080)(false) // vs. idiomatic Kotlin val c2 = connect("example.com", port = 8080, useTls = false) ``` The idiomatic version wins on: - **Readability** — reads as one call with named slots. - **Tooling** — parameter hints, autocompletion, and refactoring all understand it. - **Allocation** — no intermediate function objects per argument. - **inline interop** — when passed lambdas matter, Kotlin's `inline` functions can erase the lambda allocation entirely; curried chains cannot generally be inlined. ## When functional encodings still win Default arguments are **resolved at the call site** — you cannot leave the call "partially made" and pass it around. You need a real closure / curried function when you must produce a **first-class function of the remaining arguments**: ```kotlin val handlers: List<(Event) -> Unit> = listOf( { e -> log("INFO", e) }, // partial application of log, fixing the level { e -> log("WARN", e) } ) ``` Here each element is a function value passed into a list — defaults can't express that. ## Rule of thumb - Fixing arguments **at a call you're making now** → default + named arguments. - Producing a **reusable function** of the remaining arguments to pass elsewhere → closure / partial application (or a curried function).

  • Give a case where default arguments cannot replace partial application.
    When you need the partially-applied result as a value to pass into a higher-order function (e.g., a list of (Event) -> Unit handlers each fixing a log level). Defaults are resolved at the call and can't be carried around unfinished.
  • Does using default arguments cost any extra allocation compared to a curried chain?
    No — a defaulted call compiles to a normal invocation (with a synthetic default-args dispatch), allocating no per-argument function objects, unlike a curried chain that creates a closure at each stage.

saying these in an interview costs you the question

  • Claiming currying/partial application is generally preferable in idiomatic Kotlin
  • Not knowing default arguments are resolved at the call site
  • Asserting default arguments can be 'handed off' partially completed
  • Missing that closures are needed when you must pass a first-class remaining-args function

context

open as a page

What is currying in Kotlin, and how do you encode a two-argument function like add(a, b) as a curried function using returned lambdas?

level: juniorimportance: should knowfreq 35%

basics

~20 s

Currying turns a function that takes many arguments into a chain of functions that each take one argument and return the next function. You do it in Kotlin by returning a lambda from a lambda.

open as a page

How do you implement partial application in Kotlin by capturing arguments in a closure, and how does it differ from currying?

level: middleimportance: should knowfreq 40%

basics

~10 s

Partial application means fixing some of a function's arguments now and getting back a new function that takes the rest. In Kotlin you do it by returning a lambda that captures the fixed arguments.

open as a page

When you partially apply a function by capturing a mutable variable in a closure, what surprising behavior can occur, and how do you make the captured value stable?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Kotlin closures capture variables by reference, not by snapshot. If you capture a var and later change it, the partially applied function sees the new value. Capture a val (or copy the value into one) to lock it in.

open as a page

Write a generic curry/uncurry pair for two-argument functions in Kotlin and explain the closures and types involved. What are the runtime costs?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Write an extension that turns an (A, B) -> R into (A) -> (B) -> R by returning nested lambdas, and an uncurry that does the reverse. Each curried stage creates a small function object, so there's a tiny allocation cost.

open as a page