Destructuring Declarations
Reviewed & published by Brayan K
A destructuring declaration unpacks an object into several separate variables in a single statement, as in val (x, y) = point, pulling each component out by position.
Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
It shines with pairs, data classes, map entries, and lambda parameters, making code that handles grouped values dramatically cleaner.
What You'll Learn in This Lesson
- Unpacking a Pair or Triple into variables
- Destructuring data classes via componentN()
- Looping over map entries as (key, value)
- Skipping unwanted components with _
- Destructuring lambda parameters inline
- Why destructuring is positional, not by name
1️⃣ Destructuring Pairs & Data Classes
The simplest case unpacks a Pair or Triple into named values. Behind the scenes Kotlin calls component1(), component2(), and so on — so anything that defines those operators can be destructured.
fun main() {
val point = Pair(3, 7)
// Destructure a Pair into two named values at once:
val (x, y) = point
println("x = $x, y = $y") // -> x = 3, y = 7
// Works for a Triple too:
val (r, g, b) = Triple(255, 128, 0)
println("$r,$g,$b") // -> 255,128,0
}A data class automatically generates those componentN() functions, so its instances destructure too — handy for returning several values from a function:
// Data classes auto-generate component1(), component2(), ...
data class User(val name: String, val age: Int)
fun main() {
val user = User("Ada", 36)
// Destructure by POSITION (order of the properties):
val (name, age) = user
println("$name is $age") // -> Ada is 36
// A function can return several values via a data class:
fun minMax(): Pair<Int, Int> = Pair(2, 9)
val (low, high) = minMax()
println("range $low..$high") // -> range 2..9
}2️⃣ Destructuring in for Loops over Maps
Iterating a Map is where destructuring pays off most. Each entry unpacks into (key, value), so the loop reads like plain English instead of repeatedly calling .key and .value.
fun main() {
val ages = mapOf(
"Ada" to 36,
"Linus" to 54
)
// Each entry destructures into (key, value):
for ((name, age) in ages) {
println("$name -> $age")
}
}3️⃣ Skipping with _ and Lambdas
When a type has more components than you need, mark the ones to ignore with an underscore _. You can also destructure right in a lambda's parameter list, which is perfect for collection operations.
data class Person(val first: String, val last: String, val city: String)
fun main() {
val p = Person("Grace", "Hopper", "NYC")
// Use _ to SKIP components you don't need:
val (first, _, city) = p
println("$first from $city") // -> Grace from NYC
// In a lambda, destructure each pair directly:
val scores = listOf("Ada" to 90, "Bo" to 75)
scores.forEach { (name, score) ->
println("$name: $score")
}
}Your turn. Fill in the ___ blanks, then run and compare.
// 🎯 YOUR TURN — replace each ___ then run main()
data class Book(val title: String, val pages: Int)
fun main() {
val book = Book("Kotlin", 320)
// 1) Destructure the book into title and pages
val (___, ___) = book // 👉 (title, pages)
println("$title has $pages pages")
// ✅ Expected:
// Kotlin has 320 pages
}Mini-Challenge: Coordinate Report
Loop a list of coordinate pairs with destructuring and print each point's sum.
// 🎯 MINI-CHALLENGE: a coordinate report.
// Save as Coords.kt and run main().
//
// Given a list of (x, y) pairs:
// listOf(1 to 2, 3 to 4, 5 to 6)
// Loop with destructuring (x, y) and print "Point (x, y): sum = x+y"
// for each one.
//
// ✅ Expected output:
// Point (1, 2): sum = 3
// Point (3, 4): sum = 7
// Point (5, 6): sum = 11
fun main() {
val points = listOf(1 to 2, 3 to 4, 5 to 6)
// your code here
}Common Errors (and the fix)
- ❌ Assuming destructuring matches by NAME. ✅ It is positional — the first variable always gets component1().
- ❌ Trying to destructure a regular (non-data) class with no componentN(). ✅ Make it a data class or add operator functions.
- ❌ Declaring extra variables you don't use, causing warnings. ✅ Skip them with _.
- ❌ Forgetting parentheses in a lambda: forEach { name, score -> }. ✅ Destructured params need parens: forEach { (name, score) -> }.
Pro Tips
- 💡 Return multiple values from a function with a small data class instead of out-parameters.
- 💡 Map loops: for ((k, v) in map) is the idiomatic way to walk a map.
- 💡 Underscore freely to keep only the components you actually use.
📋 Quick Reference — Destructuring
| Use case | Kotlin Syntax |
|---|---|
| Pair / Triple | val (x, y) = pair |
| Data class | val (name, age) = user |
| Map entry | for ((k, v) in map) |
| Skip a slot | val (a, _, c) = triple |
| In a lambda | .map { (k, v) -> ... } |
🎉 Lesson Complete!
- ✅ val (a, b) = thing unpacks into separate variables
- ✅ It works via componentN() — Pair, Triple, and data classes supply them
- ✅ Destructuring is positional, not by name
- ✅ for ((k, v) in map) reads map entries cleanly
- ✅ Use _ to skip, and destructure right in lambda parameters
Practice quiz
What does 'val (a, b) = thing' translate into under the hood?
- val a = thing.component1() and val b = thing.component2()
- Two calls to toString()
- A map lookup by name
- A copy() call
Answer: val a = thing.component1() and val b = thing.component2(). Destructuring calls component1(), component2(), and so on by position.
Is destructuring positional or by name?
- By the names you choose
- Strictly positional
- Alphabetical by property name
- Random
Answer: Strictly positional. It is strictly positional; the first variable gets component1() regardless of its name.
How do you skip a component you don't need?
- Use the keyword skip
- Leave a blank space
- Use null in that slot
- Use an underscore _ in that position
Answer: Use an underscore _ in that position. An underscore _ discards the component in that position.
How does each Map entry destructure in a for loop?
- Into (key, value)
- Into (value, key)
- Into a single Pair object only
- It cannot be destructured
Answer: Into (key, value). for ((key, value) in map) unpacks each entry into key and value.
Which types support destructuring out of the box?
- Only data classes
- Only Pair
- Pair, Triple, Map entries, and data classes
- Every class automatically
Answer: Pair, Triple, Map entries, and data classes. Pair, Triple, Map entries, and data classes all provide componentN() automatically.
For 'data class User(val name: String, val age: Int)', what does 'val (age, name) = user' assign to your 'age' variable?
- The user's name (component1)
- The user's age
- null
- A compile error
Answer: The user's name (component1). Positional matching means your first variable gets component1(), which is name.
Can a regular (non-data) class with no componentN() be destructured?
- Yes, always
- No, not without componentN() operators
- Only if it is open
- Only inside a loop
Answer: No, not without componentN() operators. Without componentN() operators a plain class cannot be destructured.
What is the correct lambda syntax to destructure a Pair parameter?
- forEach { name, score -> }
- forEach { name; score -> }
- forEach { [name, score] -> }
- forEach { (name, score) -> }
Answer: forEach { (name, score) -> }. Destructured lambda parameters need parentheses: { (name, score) -> }.
What does a data class auto-generate to enable destructuring?
- componentN() functions
- An iterator
- A toString only
- A compareTo only
Answer: componentN() functions. Data classes auto-generate component1(), component2(), and so on.
Where is destructuring especially idiomatic?
- Only in constructors
- Iterating a Map with for ((k, v) in map)
- Only in test code
- Inside string templates
Answer: Iterating a Map with for ((k, v) in map). Looping a Map with for ((k, v) in map) is a classic, readable use of destructuring.
Continue this course
- Previous: Visibility Modifiers
- Next: lateinit & lazy — Initialize non-null properties later, or compute them once on first access
- Quick reference: Kotlin cheat sheet
Frequently asked questions
How does destructuring actually work under the hood?
When you write val (a, b) = thing, Kotlin translates it into val a = thing.component1() and val b = thing.component2(). Any type that provides operator functions named component1(), component2(), and so on can be destructured. Pair, Triple, Map entries, and every data class supply these automatically, which is why they all work out of the box.
Is destructuring positional or by name?
It is strictly positional. The first name you write receives component1() — the first declared property — regardless of what you call it. So for data class User(val name: String, val age: Int), writing val (age, name) = user would put the user's name into your age variable. Always match the order of the class's properties, not the names.
How do I ignore a component I don't need?
Use an underscore in that position. In val (first, _, city) = person the middle component is computed but discarded, so you don't introduce an unused variable. The underscore is purely a placeholder — it tells the compiler 'skip this slot' and keeps your code clean when a class has more properties than you care about.
Where is destructuring most useful in real code?
Three places dominate. First, iterating a Map with for ((key, value) in map) reads far better than calling .key and .value. Second, returning multiple results from a function via Pair, Triple, or a small data class. Third, lambda parameters — collection functions like forEach and map let you destructure each element inline, as in list.map { (name, score) -> ... }.