skip to content

How do you destructure a parameter inside a lambda, and what are the rules and limits of destructuring lambda parameters?

level: middleimportance: should knowfreq 60%

answer

  1. Parentheses { (a, b) -> } destructure ONE param
  2. Powered by component1()/component2() operators
  3. Pair, Triple, Map.Entry, data class get componentN free
  4. { a, b -> } = two params, NOT destructuring
  5. _ skips unused components; positional order

basics

~20 s

If a lambda parameter is something like a Pair or a data class, you can split it into its parts inside parentheses, e.g. map.forEach { (key, value) -> ... }, instead of using one name and calling .first/.second.

solid answer

~40 s

Kotlin lets a **single** lambda parameter be destructured into its components using parentheses: `{ (a, b) -> ... }`. This works for any type that provides `componentN()` operator functions — `Pair`, `Triple`, `Map.Entry`, and `data class`es get these automatically. So `mapOf(...).forEach { (key, value) -> ... }` unpacks each `Map.Entry` via `component1()`/`component2()`. Critically, destructuring is still **one** parameter, not two — `{ (a, b) -> }` differs from `{ a, b -> }` (two separate parameters). You can skip components you don't need with `_`: `{ (_, value) -> }`. You may also add explicit types: `{ (a: Int, b: String) -> }`. Because it's positional, you can't reorder by name; order follows `componentN()`. Over-destructuring (more components than the type provides) won't compile.

code

kotlin · 12 lines
kotlin
data class User(val id: Int, val name: String)

val users = listOf(User(1, "Ann"), User(2, "Bo"))

// Destructure the single User parameter
val names = users.map { (_, name) -> name } // ["Ann", "Bo"]

// Map.Entry destructured into key/value
mapOf(1 to "x").forEach { (k, v) -> println("$k -> $v") }

// Contrast: TWO parameters (reduce passes acc + element)
val sum = listOf(1, 2, 3).reduce { acc, e -> acc + e } // 6

go deeper

for a junior

Recognizes (key, value) -> syntax for maps and can read it, even if shaky on the underlying mechanism.

for a middle

Explains it is one destructured parameter backed by componentN(), distinguishes it from two-parameter lambdas, and uses _ for unused parts.

for a senior

Calls out positional fragility, knows which types provide componentN by default, and when explicit names beat destructuring for clarity.

for a principal

Considers API/data-class design impact: how property reordering silently breaks destructuring call sites and guides conventions to mitigate it.

## What destructuring a lambda parameter means Normally a lambda parameter is a single name. **Destructuring** lets you split one parameter into its parts directly in the parameter list, using **parentheses**: ```kotlin val entries = mapOf("a" to 1, "b" to 2) entries.forEach { (key, value) -> println("$key=$value") } ``` Here `forEach` on a `Map` passes a single `Map.Entry`. The `(key, value)` syntax unpacks that one entry into two names. ## How it works under the hood — `componentN()` Destructuring relies on **operator functions** named `component1()`, `component2()`, etc. The compiler rewrites `(a, b)` into: ```kotlin val a = entry.component1() val b = entry.component2() ``` Types that supply these automatically: - **`data class`** — generates `componentN()` for each primary-constructor property in declaration order. - **`Pair`** (`component1`=first, `component2`=second), **`Triple`**, and **`Map.Entry`** (`key`, `value`). Any type can opt in by declaring `operator fun componentN()`. ```kotlin data class Point(val x: Int, val y: Int) listOf(Point(1, 2)).map { (x, y) -> x + y } // 3 ``` ## One parameter, not many This is the classic trap: **destructuring is still a single parameter.** - `{ a, b -> ... }` = **two** parameters (e.g. `reduce`, `foldIndexed`-style callbacks). - `{ (a, b) -> ... }` = **one** parameter destructured into two. Using the wrong one is a compile error or a logic bug. ## Skipping and typing components - Use **`_`** for components you don't need: `{ (_, value) -> value }` ignores the key. - Add **explicit types** if desired: `{ (a: Int, b: String) -> }`. - Positional only: order follows `componentN()`, you cannot reorder by property name. ## Limits - You cannot destructure more components than the type provides — it won't compile. - Destructuring is **positional**, so a property reorder in a data class silently changes which name binds to which value. ## Summary Wrap parts in `()` to destructure one lambda parameter via `componentN()`; available for `Pair`, `Triple`, `Map.Entry`, and data classes; use `_` to drop unused parts; remember it is still ONE parameter.

  • What's the difference between `{ a, b -> }` and `{ (a, b) -> }`?
    `{ a, b -> }` declares two separate parameters; `{ (a, b) -> }` declares one parameter and destructures it into two via componentN(). They are not interchangeable.
  • Why might destructuring be fragile if a data class evolves?
    Because it is positional. Reordering the constructor properties changes which value `component1()` returns, so the same `(a, b)` now binds different fields without any compile error.

saying these in an interview costs you the question

  • Saying `{ (a, b) -> }` means two parameters
  • Not knowing componentN() drives destructuring
  • Thinking destructuring matches by property name, not position
  • Unaware Pair/Map.Entry/data class get componentN automatically
  • Not knowing `_` can skip unused components

context