On this page24 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is Destructuring?
- 2. Destructuring a Positional Record
- 3. Destructuring Named Fields
- 4. The Shape Must Match
- 5. Destructuring a List
- 6. When the List Length Doesn't Match
- 7. Rest Elements with ...
- 8. Skipping Values with _
- 9. final Destructuring
- 10. Swapping Two Variables
- 11. Destructuring in a for-in Loop
- 12. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 78 — Destructuring
| Category | 13 — Patterns & Modern Dart |
| Lecture | 078 of 140 |
| Estimated video length | 10 min |
| Target | Dart 3.12.2 |
| Level | Intermediate |
A record or a list holds several values, but reading them one by one with
.$1,.nameor[0]is slow and noisy. Destructuring unpacks all of them into named variables in a single line.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2, so you will recognize it when you meet it yourself.
Prerequisites
Students should already understand, from Lectures 027, 039–040, 050, 076 and 077:
for-inloops, lists and indexes- looping over
map.entrieswithentry.keyandentry.value - records, both positional
(String, int)and named({String name, int age}) - what a pattern is: a shape that a value is checked against
Connection With Previous Lectures
In Lecture 076, a function returned a record so it could hand back two values at once:
(String, int) getStudent() => ('Ali', 20);
void main() {
var student = getStudent();
var name = student.$1;
var age = student.$2;
print('$name is $age');
}Ali is 20Three lines just to get two values out, and $1 and $2 say nothing about what they hold.
Lecture 077 showed that a pattern can describe the shape of a value. Today we use that shape
to take the value apart directly into variables.
Learning Objectives
By the end of this lecture you will be able to:
- Explain what destructuring means
- Destructure positional and named records into variables
- Destructure a list, and predict the error when its length doesn't match
- Collect leftover list elements with a rest element
... - Skip values you don't need with the wildcard
_ - Declare destructured variables with
final - Swap two variables in one line
- Destructure records and map entries in a
for-inloop
1. What Is Destructuring?
Destructuring means writing a pattern on the left of = so that Dart unpacks the value on
the right into several variables at once.
Analogy — a lunchbox with three compartments. Instead of reaching into "compartment 1", "compartment 2" and "compartment 3" every time, you take everything out once and put each item on its own labeled plate.
The pattern looks like the value it unpacks: parentheses for a record, square brackets for a list.
2. Destructuring a Positional Record
Put the variable names in parentheses after var:
(String, int) getStudent() => ('Ali', 20);
void main() {
var (name, age) = getStudent();
print('$name is $age');
}Ali is 20The first field goes into name, the second into age. Each variable gets its type from its
field: name is a String, age an int.
3. Destructuring Named Fields
For a record with named fields, write :name. The colon means "a variable with the same name
as the field":
({String name, int age}) getStudent() => (name: 'Sara', age: 19);
void main() {
var (:name, :age) = getStudent();
print('$name is $age');
}Sara is 19Named fields are matched by name, so var (:age, :name) works too. To use a different
variable name, write the field, a colon, then your name: var (name: who, age: years).
4. The Shape Must Match
A record pattern must have exactly the fields the record has. Dart checks this before the program runs:
void main() {
var (a, b) = ('Ali', 20, true);
print(a);
}Error: The matched value of type '(String, int, bool)' isn't assignable to the required type '(Object?, Object?)'.
var (a, b) = ('Ali', 20, true);
^A three-field record cannot be unpacked into two variables. Named fields work the same way: a misspelled or missing name is an error.
5. Destructuring a List
Use square brackets to unpack a list by position:
void main() {
var marks = [80, 75, 90];
var [math, science, english] = marks;
print('Math $math, Science $science, English $english');
}Math 80, Science 75, English 90math gets index 0, science index 1, english index 2 — all int.
6. When the List Length Doesn't Match
A record's size is part of its type, but a list's length is only known while the program runs. So a wrong length is not caught in advance — it crashes the program:
void main() {
var marks = [80, 75];
var [math, science, english] = marks;
print('$math $science $english');
}Unhandled exception:
Bad state: Pattern matching errorThe pattern wants exactly three elements; the list has two. Only destructure a list this way when you are sure of its length.
7. Rest Elements with ...
A rest element ...name collects all the remaining elements into a new list:
void main() {
var scores = [95, 88, 72, 64];
var [top, ...others] = scores;
print('Top: $top');
print('Others: $others');
}Top: 95
Others: [88, 72, 64]The rest can sit anywhere, and without a name it just skips the middle. Now the length only has to be "at least" the number of named elements:
void main() {
var temps = [21.5, 23.0, 25.5, 22.0];
var [first, ..., last] = temps;
print('First $first, last $last');
}First 21.5, last 22.08. Skipping Values with _
When you don't need a value, write the wildcard _. It matches the value and throws it
away:
(String, int, String) getStudent() => ('Zayan', 6, 'Karachi');
void main() {
var (name, _, city) = getStudent();
print('$name lives in $city');
var [gold, _, bronze] = ['Sara', 'Ali', 'Zayan'];
print('Gold $gold, bronze $bronze');
}Zayan lives in Karachi
Gold Sara, bronze Zayan_ is not a variable — you can use it many times, but you cannot read it back.
9. final Destructuring
Write final instead of var, and every unpacked variable becomes final:
void main() {
final (name, balance) = ('Ali', 1500.0);
print('$name has $balance');
}Ali has 1500.0As in Lecture 006, assigning balance = 2000.0; afterwards would be an error.
10. Swapping Two Variables
A pattern also works in an assignment to variables that already exist. Write it without
var:
void main() {
var a = 'Ali';
var b = 'Sara';
(a, b) = (b, a);
print('a: $a, b: $b');
}a: Sara, b: AliDart builds the record (b, a) first — ('Sara', 'Ali') — and then unpacks it into a and
b. No temporary variable is needed.
11. Destructuring in a for-in Loop
The loop variable can be a pattern too. With a list of records:
void main() {
var students = [('Ali', 72), ('Sara', 95)];
for (var (name, mark) in students) {
print('$name: $mark');
}
}Ali: 72
Sara: 95Lecture 050 promised a shorter way to loop over a map. Each entry is a MapEntry, and
MapEntry(:key, :value) unpacks its two properties:
void main() {
var prices = {'Milk': 250, 'Bread': 120};
for (var MapEntry(:key, :value) in prices.entries) {
print('$key costs $value');
}
}Milk costs 250
Bread costs 120Rename them with MapEntry(key: item, value: price). This pattern unpacks an object by its
properties; we study object patterns properly in Lecture 080 — Object & Map Patterns.
12. A Real-World Example
(String, double, double) dayReport(List<double> temps) {
var [first, ..., last] = temps;
var high = temps.reduce((a, b) => a > b ? a : b);
return ('${temps.length} readings', high, last - first);
}
void main() {
var week = {
'Monday': [18.0, 24.5, 21.0],
'Tuesday': [17.5, 26.0, 22.5],
};
for (final MapEntry(key: day, value: temps) in week.entries) {
final (count, high, change) = dayReport(temps);
print('$day: $count, high $high, change $change');
}
}Monday: 3 readings, high 24.5, change 3.0
Tuesday: 3 readings, high 26.0, change 5.0A rest element picks the first and last reading, a record returns three results, and the loop unpacks both each map entry and each report.
Code Examples
Example 1 — Positional Record
void main() {
var (city, temp) = ('Lahore', 31);
print('$city: $temp°C');
}Lahore: 31°CExample 2 — Named Record
void main() {
var (:name, :age) = (name: 'Zayan', age: 6);
print('$name is $age');
}Zayan is 6Example 3 — Renaming Named Fields
void main() {
var (name: who, age: years) = (name: 'Sara', age: 19);
print('$who, $years');
}Sara, 19Example 4 — List with a Rest Element
void main() {
var [first, second, ...rest] = [1, 2, 3, 4, 5];
print('$first $second $rest');
}1 2 [3, 4, 5]Example 5 — Wildcards
void main() {
var [_, middle, _] = ['Ali', 'Sara', 'Zayan'];
print(middle);
}SaraExample 6 — Swapping
void main() {
var left = 10;
var right = 20;
(left, right) = (right, left);
print('$left $right');
}20 10Example 7 — Student Marks
(int, double) summary(List<int> marks) {
var total = marks.reduce((a, b) => a + b);
return (total, total / marks.length);
}
void main() {
final (total, average) = summary([80, 75, 85]);
print('Total: $total');
print('Average: $average');
}Total: 240
Average: 80.0How it works
summaryreturns two results as one record.final (total, average)unpacks them;totalis anint,averageadouble.
Example 8 — Shopping Total
void main() {
var cart = [('Notebook', 500, 3), ('Pen', 40, 5)];
var total = 0;
for (var (item, price, qty) in cart) {
print('$item x$qty = ${price * qty}');
total += price * qty;
}
print('Total: $total');
}Notebook x3 = 1500
Pen x5 = 200
Total: 1700How it works
- Each cart line is a three-field record.
- The loop unpacks every record into
item,priceandqty.
Example 9 — Bank Balance
void main() {
var balances = {'Ali': 1500.0, 'Sara': 2487.5};
for (final MapEntry(key: owner, value: amount) in balances.entries) {
print('$owner: $amount');
}
}Ali: 1500.0
Sara: 2487.5How it works
balances.entriesgives oneMapEntryper account.- The pattern unpacks
keyintoownerandvalueintoamount.
Example 10 — Temperature Range
void main() {
var readings = [22.0, 25.5, 28.0, 24.5];
var [morning, ..., night] = readings;
print('Morning $morning, night $night');
print('Change: ${night - morning}');
}Morning 22.0, night 24.5
Change: 2.5How it works
morningis the first reading andnightthe last;...skips the middle.- The list needs at least two elements, or the program crashes.
Common Beginner Mistakes
1. Unpacking a list of the wrong length
void main() {
var marks = [80, 75, 90];
// var [a, b] = marks; // ✗ three elements, two variables
}Unhandled exception:
Bad state: Pattern matching errorThis only fails while running. Use var [a, b, ...] = marks; if extra elements are fine.
2. A record pattern of the wrong shape
void main() {
// var (a, b) = ('Ali', 20, true); // ✗
}Error: The matched value of type '(String, int, bool)' isn't assignable to the required type '(Object?, Object?)'.3. Misspelling a named field
({String name, int age}) getStudent() => (name: 'Ali', age: 20);
void main() {
// var (:name, :marks) = getStudent(); // ✗ no field called marks
}Error: The matched value of type '({int age, String name})' isn't assignable to the required type '({Object? marks, Object? name})'.4. Using brackets for a record (or parentheses for a list)
void main() {
// var (x, y) = [1, 2]; // ✗
}Error: The matched value of type 'List<int>' isn't assignable to the required type '(Object?, Object?)'.Records use ( ), lists use [ ].
5. Reading _ as if it were a variable
void main() {
var [x, _, _] = [1, 2, 3];
// print(_); // ✗
}Error: Undefined name '_'.6. Two rest elements
void main() {
// var [a, ...b, c, ...d] = [1, 2, 3, 4]; // ✗
}Error: At most one rest element is allowed in a list or map pattern.Best Practices
- Destructure a returned record right away instead of reading
.$1and.$2. - Prefer named fields and
:namewhen a record has more than two values. - Destructure a list without
...only when its length is guaranteed. - Use
_for values you don't need, so readers know they are ignored on purpose. - Use
finalwhen the unpacked values should not change. - Loop over
map.entrieswithMapEntry(:key, :value), renaming to meaningful names.
Mini Challenge
Write a function minMax(List<int> marks) that returns the lowest and highest mark as a
record. In main, destructure the result with final for the marks [72, 95, 88].
Your program should print exactly:
Lowest: 72, highest: 95Try it yourself before opening the solution.
Show solution
(int, int) minMax(List<int> marks) {
var sorted = [...marks]..sort();
var [low, ..., high] = sorted;
return (low, high);
}
void main() {
final (lowest, highest) = minMax([72, 95, 88]);
print('Lowest: $lowest, highest: $highest');
}Lowest: 72, highest: 95How it works
- A sorted copy puts the lowest mark first and the highest last.
[low, ..., high]takes both ends; the record carries them back tomain.
Practice Questions
Work through these in order — they run from beginner to application to challenge.
Beginner
- Destructure
('Karachi', 34)intocityandtemp, and printKarachi: 34. - Destructure
(name: 'Ali', age: 20)with:nameand:age, and printAli, 20. - Destructure
[10, 20, 30]into three variables and print their sum.
Application
- From
['Sara', 'Ali', 'Zayan', 'Omar'], take the first name and put the rest in a list. PrintSaraand[Ali, Zayan, Omar]. - From the record
('Ali', 20, 'Lahore'), print onlyAli from Lahore, using_. - Start with
var x = 3;andvar y = 7;. Swap them in one line and print7 3. - Loop over
{'Pen': 40, 'Bag': 1500}withMapEntry(:key, :value)and print each line asPen: 40.
Challenge
-
Write
split(double bill)that returns(tip, total)for a 10% tip. Destructuresplit(2000)withfinaland printTip: 200.0, total: 2200.0. -
Loop over
[('Ali', 72, 80), ('Sara', 95, 91)](name, math, science), destructuring each record, and printAli: 152andSara: 186. -
This program crashes. Explain why in one sentence, then fix it with a rest element so it prints
95 64:void main() { var scores = [95, 88, 72, 64]; var [first, last] = scores; print('$first $last'); }
Practice Answers
Question 1 — Solution
void main() {
var (city, temp) = ('Karachi', 34);
print('$city: $temp');
}Karachi: 34Question 2 — Solution
void main() {
var (:name, :age) = (name: 'Ali', age: 20);
print('$name, $age');
}Ali, 20Question 3 — Solution
void main() {
var [a, b, c] = [10, 20, 30];
print(a + b + c);
}60Question 4 — Solution
void main() {
var [first, ...rest] = ['Sara', 'Ali', 'Zayan', 'Omar'];
print(first);
print(rest);
}Sara
[Ali, Zayan, Omar]Question 5 — Solution
void main() {
var (name, _, city) = ('Ali', 20, 'Lahore');
print('$name from $city');
}Ali from LahoreQuestion 6 — Solution
void main() {
var x = 3;
var y = 7;
(x, y) = (y, x);
print('$x $y');
}7 3Question 7 — Solution
void main() {
var stock = {'Pen': 40, 'Bag': 1500};
for (var MapEntry(:key, :value) in stock.entries) {
print('$key: $value');
}
}Pen: 40
Bag: 1500Question 8 — Solution
(double, double) split(double bill) {
var tip = bill * 0.1;
return (tip, bill + tip);
}
void main() {
final (tip, total) = split(2000);
print('Tip: $tip, total: $total');
}Tip: 200.0, total: 2200.0Question 9 — Solution
void main() {
var results = [('Ali', 72, 80), ('Sara', 95, 91)];
for (var (name, math, science) in results) {
print('$name: ${math + science}');
}
}Ali: 152
Sara: 186Question 10 — Solution
The pattern [first, last] needs exactly two elements, but the list has four, so matching
fails while the program runs. ... skips the middle:
void main() {
var scores = [95, 88, 72, 64];
var [first, ..., last] = scores;
print('$first $last');
}95 64Quick Revision
- What does destructuring do?
- Which brackets unpack a record, and which a list?
- What does
:namemean invar (:name, :age) = ...? - Why is a wrong record shape a compile error, but a wrong list length a runtime crash?
- What does
...restcollect? - Can you read the value matched by
_? - How do you swap
aandbwithout a temporary variable? - How do you unpack each entry when looping over
map.entries?
Key Takeaways
- Destructuring puts a pattern on the left of
=to unpack a value into variables. var (a, b) = record;unpacks by position;var (:name, :age) = record;by field name.- A record pattern must match the record's shape exactly, or the code does not compile.
var [a, b, c] = list;needs exactly that many elements, or it crashes while running....restcollects the remaining elements; a bare...skips them. Only one per pattern._matches a value and discards it.final (a, b) = ...makes every unpacked variable final.(a, b) = (b, a);swaps two existing variables.for (var MapEntry(:key, :value) in map.entries)unpacks each map entry in the loop.
What's Next?
Lecture 79 — Pattern Types. Today we used a handful of patterns — records, lists,
wildcards, rest elements — mainly to unpack values. Dart has many more kinds: patterns that
check for null, compare values, test a type, or combine other patterns with && and ||.
Next we will tour the full set and see what each one matches.