skip to content

How does destructuring work in lambda parameters, and what are the gotchas around the single-vs-multiple-parameter distinction?

level: seniorimportance: nice to knowfreq 30%

answer

  1. Parens = ONE param destructured, not two
  2. { a, b -> } is two params; { (a,b) -> } is one unpacked
  3. Needs single arg with componentN()
  4. Underscore + type annotations allowed
  5. Still positional inside the lambda

basics

~10 s

A lambda parameter wrapped in parentheses is destructured into its components instead of received whole. { (k, v) -> ... } takes ONE entry parameter and unpacks it, not two separate parameters.

solid answer

~40 s

When a lambda receives a single object that supports `componentN()`, you can destructure it inline by wrapping the parameter in parentheses: `map.forEach { (key, value) -> ... }`. The crucial gotcha is that `(key, value)` is **one** parameter being unpacked, not two parameters — so this only works when the lambda's actual single argument is destructurable (a `Map.Entry`, `Pair`, data class, etc.). A two-parameter lambda is written without parentheses: `{ a, b -> ... }`. Underscores work here too (`{ (_, v) -> }`), and you can annotate types: `{ (k: String, v: Int) -> }`. Destructured lambda params are a syntactic convenience; the receiver lambda still gets exactly one argument from the caller. This matters for higher-order APIs like `forEach`, `map`, `associate`, and coroutine flows that emit `Pair`/data classes.

code

kotlin · 5 lines
kotlin
val pairs = listOf(1 to "one", 2 to "two")
pairs.forEach { (number, word) -> println("$number=$word") }      // one Pair, unpacked
pairs.map { (n, _) -> n * 10 }                                     // skip the word
// contrast: a genuine two-arg lambda has no parens
val add: (Int, Int) -> Int = { a, b -> a + b }

go deeper

for a junior

Recognizes { (k, v) -> } in map iteration but may not grasp it's one parameter.

for a middle

Clearly distinguishes the one-param destructured form from a two-param lambda.

for a senior

Explains the requirement for a single destructurable argument, underscore/type usage, and applies it to withIndex/Pair-based APIs.

for a principal

Discusses readability trade-offs and how positional destructured params interact with API stability when the underlying tuple's order changes.

## The feature If a lambda takes a single argument that has `componentN()` operators, you may destructure it directly in the parameter list by **parenthesizing** the names: ```kotlin mapOf("a" to 1).forEach { (key, value) -> println("$key=$value") } ``` Here `forEach` on a map passes one `Map.Entry`; `(key, value)` unpacks it via `component1()`/`component2()`. ## The single-vs-multiple gotcha This is the classic confusion: - `{ (a, b) -> ... }` — **one** parameter, destructured into `a` and `b`. Requires that single arg to support `component1()`/`component2()`. - `{ a, b -> ... }` — **two** separate parameters. No destructuring. They are not interchangeable. If an API hands you two arguments (e.g. `Map.reduce`-style or a `(K, V) -> R` functional type), you must use the no-parens form. If it hands you one `Pair`, use parens. ## Underscores and types ```kotlin listOf(1 to "a").forEach { (_, label) -> println(label) } // skip first listOf(1 to "a").map { (n: Int, s: String) -> "$n$s" } // typed ``` Underscore skips a component (and its `componentN()` call). Type annotations are allowed per element. ## Where it shines - `Map.forEach`, `entries.map { (k, v) -> ... }` - `withIndex()`: `list.withIndex().forEach { (i, x) -> ... }` (destructures `IndexedValue`) - Working with `Pair`-emitting sequences/flows. ## What it is NOT - It does not let a lambda magically accept multiple arguments as one tuple — Kotlin has no tuple type beyond `Pair`/`Triple`. The single argument must already be a destructurable object. - It's still positional: `{ (value, key) -> }` over a map entry would bind the *key* to a variable named `value`. ## Runtime model Like all destructuring, this is sugar: the lambda receives one argument and the body begins with `componentN()` calls.

  • Does `{ (a, b) -> }` mean the lambda takes two parameters?
    No. It takes ONE parameter and destructures it into a and b. A two-parameter lambda is `{ a, b -> }` without parentheses.
  • Can you use an underscore in a destructured lambda parameter?
    Yes: `{ (_, value) -> ... }` skips the first component and doesn't emit its componentN() call, just like in a val destructuring.

Parentheses are like opening one delivered box at the door; no parentheses is two separate deliveries.

saying these in an interview costs you the question

  • Confusing { (a,b) -> } (one param) with { a, b -> } (two params)
  • Thinking destructuring lets a lambda accept multiple raw args as a tuple
  • Forgetting the single argument must support componentN()
  • Ignoring that binding inside the parens is still positional

context