skip to content

Explain how destructuring interacts with `for` loops, lambda parameters, and the stdlib collection APIs. Where do the componentN() functions come from in each case?

level: seniorimportance: should knowfreq 40%

answer

  1. Map.Entry component1/2 are stdlib extensions
  2. Lambda `(k,v)` is ONE param, not two
  3. IndexedValue + Pair/Triple are data classes
  4. withIndex()/mapIndexed for index destructuring
  5. `_` and explicit types allowed in lambda destructuring

basics

~20 s

You can unpack each element of a loop or each lambda argument using parentheses, e.g. for ((k, v) in map) or list.map { (a, b) -> ... }. It works because the element type provides component1(), component2(), etc.

solid answer

~40 s

Destructuring in `for` loops and lambda parameters reuses the same `componentN()` convention. `for ((k, v) in map)` works because `Map.Entry` has extension `component1()` (key) and `component2()` (value) in the stdlib; iteration yields entries that are then destructured. In lambdas, a parenthesized parameter `{ (a, b) -> ... }` is still a single parameter destructured into a and b via componentN() — it does NOT change the lambda's arity. So `map.forEach { (k, v) -> }` receives one `Map.Entry`, not two args. Destructured lambda params support `_` skips and even explicit types: `{ (a: Int, b: String) -> }`. Indexed iteration uses `withIndex()`/`mapIndexed`, whose elements (`IndexedValue`) also provide component1()/component2(). The mechanism is uniform; only the source of componentN() differs.

code

kotlin · 9 lines
kotlin
fun main() {
    val m = mapOf("a" to 1, "b" to 2)
    for ((k, v) in m) println("$k=$v")
    m.forEach { (k, _) -> println(k) }            // one Entry param
    listOf("x", "y").withIndex().forEach { (i, s) -> // IndexedValue
        println("$i:$s")
    }
    listOf(1 to "a").map { (n, label) -> "$n$label" } // Pair param
}

go deeper

for a junior

Can write for ((k, v) in map) but may not know why it works.

for a middle

Knows lambda (a,b) is one destructured parameter and uses withIndex().

for a senior

Explains stdlib extension components on Map.Entry and the uniform desugaring across all contexts.

for a principal

Guides API/style policy on where positional destructuring in loops/lambdas is safe versus a maintenance hazard.

## One convention, three contexts Destructuring declarations, `for`-loop variables, and lambda parameters all desugar to the same `componentN()` calls. ## for loops ```kotlin for ((key, value) in map) { use(key, value) } ``` Iteration over a `Map` yields `Map.Entry` objects. The stdlib provides: ```kotlin operator fun <K, V> Map.Entry<K, V>.component1(): K = key operator fun <K, V> Map.Entry<K, V>.component2(): V = value ``` as **extension** functions, so each entry is destructured. The same pattern works for any iterable of a componentN()-bearing element type: ```kotlin for ((index, item) in list.withIndex()) { ... } // IndexedValue.component1/2 ``` ## Lambda parameters — arity is unchanged A parenthesized lambda parameter is **still one parameter**: ```kotlin map.forEach { (k, v) -> ... } // ONE Map.Entry param, destructured list.map { (a, b) -> a + b } // ONE element param (e.g. a Pair) ``` This is a frequent confusion: `{ (k, v) -> }` is not a two-arg lambda. You can: - Skip with `_`: `{ (_, v) -> v }` - Annotate types: `{ (a: Int, b: String) -> }` - Mix with normal params is not allowed for the single destructured slot, but multi-param lambdas can each be destructured: `{ (a, b), (c, d) -> }` only when the function type has two params. ## Where componentN() comes from per case | Context | Element type | Source of componentN() | |---|---|---| | `for ((k,v) in map)` | `Map.Entry` | stdlib extensions | | `for ((i,x) in xs.withIndex())` | `IndexedValue` | data class (it's a data class) | | `list.map { (a,b) -> }` over pairs | `Pair` | Pair is a data class | | custom `for ((a,b) in xs)` | your type | your member/extension componentN() | ## Restrictions & notes - Destructured lambda params don't introduce new scoping rules; they're just positional unpacking. - You cannot destructure a lambda parameter whose type lacks the needed componentN(). - `_` in any of these omits the corresponding call. - These are all compile-time rewrites; there's no special runtime tuple. ## Practical guidance Loop/lambda destructuring shines for maps and indexed iteration. For arbitrary element types, ensure the positional meaning is obvious; otherwise prefer named access to avoid the silent-rebinding hazard that positional binding carries everywhere.

  • Is `{ (k, v) -> }` a two-argument lambda?
    No. It's a single-parameter lambda whose one argument is destructured into k and v via componentN(); arity stays one.
  • Why can you destructure a Map.Entry in a for loop?
    The stdlib declares component1()/component2() as extension operators on Map.Entry, returning key and value.

saying these in an interview costs you the question

  • Calling `{ (k, v) -> }` a two-arg lambda
  • Thinking Map.Entry has member (not extension) components
  • Not knowing withIndex() yields IndexedValue components
  • Believing loop destructuring uses a different mechanism than val destructuring

context