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.
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?
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.
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.
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.
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.
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.
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.
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.
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.
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(...).
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