Destructuring Workbook

Ten short exercises on Kotlin destructuring — declarations, the componentN convention, data classes, map entries, withIndex, lambda parameters, skipping with underscore, and custom components.

Practice problems for Destructuring: Unpacking an Object in One Line. Each takes a minute or two. A couple of exercises auto-check: implement the function in the editor and press Run — hidden tests go green when you’re right and red (with a hint) when you’re not, on JetBrains’ Kotlin compiler server. The rest stay attempt-then-reveal: fill in the TODO, press Run, then reveal Show answer to check yourself. Nothing here is a trick question, just direct practice of the syntax from the lesson.

the basics

1. Unpack a data class

Given data class User(val id: Int, val name: String) and val u = User(1, "Ada"), pull id and name into two variables in one line.

Try it — edit, then press Run data class User(val id: Int, val name: String) fun main() { val u = User(1, "Ada") // TODO: destructure u into id and name in one line, then print them println(u) }
Show answer Hide answer
val (id, name) = u

2. What it expands to

The declaration val (id, name) = u is shorthand for what two calls?

Try it — edit, then press Run data class User(val id: Int, val name: String) fun main() { val u = User(1, "Ada") // TODO: get id and name by calling the component functions directly val id = 0 // TODO: u.component1() val name = "" // TODO: u.component2() println(id) println(name) }
Show answer Hide answer
val id = u.component1()
val name = u.component2()

3. Return two values

Implement minMax so it returns a MinMax(min, max) holding the smallest and largest of the numbers.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert data class MinMax(val min: Int, val max: Int) class Test { @Test fun minMax() { Assert.assertEquals("smallest and largest of the list", MinMax(1, 8), minMax(listOf(4, 8, 1, 6))) } } //sampleStart fun minMax(nums: List<Int>): MinMax = MinMax(0, 0) // TODO: return the min and max of nums //sampleEnd
Show answer Hide answer
data class MinMax(val min: Int, val max: Int)

fun minMax(nums: List<Int>) = MinMax(nums.min(), nums.max())

// at the call site, destructure the result:
val (low, high) = minMax(listOf(4, 8, 1, 6))

loops and lambdas

4. Destructure map entries

Given a map ages, iterate it printing "<name> is <age>", destructuring each entry.

Try it — edit, then press Run fun main() { val ages = mapOf("Ada" to 36, "Grace" to 45) // TODO: iterate ages, destructuring each entry into name and age for (entry in ages) { println(entry) } }
Show answer Hide answer
for ((name, age) in ages) {
    println("$name is $age")
}

5. Index and value

Iterate names with both the index and the value, destructured in the loop header.

Try it — edit, then press Run fun main() { val names = listOf("Ada", "Grace", "Alan") // TODO: loop with index and value, destructured in the loop header for (name in names) { println(name) } }
Show answer Hide answer
for ((i, name) in names.withIndex()) {
    println("$i: $name")
}

6. Destructure a lambda parameter

Implement labels so it maps each user to "<id>: <name>", destructuring the lambda parameter.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert data class User(val id: Int, val name: String) class Test { @Test fun labels() { Assert.assertEquals("map each user to "id: name"", listOf("1: Ada", "2: Bo"), labels(listOf(User(1, "Ada"), User(2, "Bo")))) } } //sampleStart fun labels(users: List<User>): List<String> = emptyList() // TODO: map each user to "id: name", destructuring the lambda parameter //sampleEnd
Show answer Hide answer
fun labels(users: List<User>) = users.map { (id, name) -> "$id: $name" }

skipping and custom components

7. Skip a component

Given val u = User(1, "Ada"), bind only the name, ignoring the id.

Try it — edit, then press Run data class User(val id: Int, val name: String) fun main() { val u = User(1, "Ada") // TODO: bind only the name, skipping the id with _ val name = u.name println(name) }
Show answer Hide answer
val (_, name) = u

_ skips the component without binding a variable.

8. A List destructures too

Given val parts = "host=localhost".split("="), bind the two halves to key and value.

Try it — edit, then press Run fun main() { // TODO: bind the two halves of the split to key and value in one line val parts = "host=localhost".split("=") println(parts) }
Show answer Hide answer
val (key, value) = "host=localhost".split("=")

List provides component1 through component5, so it destructures positionally.

9. Custom components

Add component1/component2 to class Pairish(val a: Int, val b: Int) so it can be destructured.

Try it — edit, then press Run class Pairish(val a: Int, val b: Int) { // TODO: add operator component1() and component2() so Pairish destructures } fun main() { // TODO: after adding the components, destructure Pairish(1, 2) into x and y val p = Pairish(1, 2) println("" + p.a + " " + p.b) }
Show answer Hide answer
class Pairish(val a: Int, val b: Int) {
    operator fun component1() = a
    operator fun component2() = b
}

val (x, y) = Pairish(1, 2)

10. Destructure a Triple

Bind the three values of Triple(1, 2, 3) to a, b, and c in one line.

Try it — edit, then press Run fun main() { val t = Triple(1, 2, 3) // TODO: bind t's three values to a, b, c in one line, then print them println(t) }
Show answer Hide answer
val (a, b, c) = Triple(1, 2, 3)

Destructuring is positional — a gets component1(), b gets component2(), and so on.


Going deeper: returning several values, and the footgun

12. Return two values cleanly

Return the min and max of a list as one destructurable result, so a caller can write val (lo, hi) = range(...).

Implement it, then press Run to check import org.junit.Test import org.junit.Assert data class MinMax(val min: Int, val max: Int) class Test { @Test fun range() { val (lo, hi) = range(listOf(3, 1, 4, 1, 5)) Assert.assertEquals(1, lo) Assert.assertEquals(5, hi) } } //sampleStart fun range(xs: List<Int>): MinMax = MinMax(0, 0) // TODO: return the min and max //sampleEnd
Show answer Hide answer
fun range(xs: List<Int>) = MinMax(xs.min(), xs.max())

A small named data class reads far better at the call site than a Pair<Int, Int>, where first/second tell you nothing.

13. The positional footgun

You have data class Point(val x: Int, val y: Int) and write val (x, y) = Point(1, 2). A teammate later reorders the constructor to Point(val y: Int, val x: Int). Does your line still compile, and what are x and y now? Reveal to check.

Show answer Hide answer

It still compiles — and silently binds the wrong values. Destructuring is positional, not by name, so x now holds what used to be y (x == 2, y == 1). Nothing warns you. This is why positional destructuring is safe for small tuples and map entries but risky for wide objects — use named access (point.x) there instead.


Back to the lesson, Destructuring, or on to the next one: visibility and packages.

Comments