Watch the video (10 min)
On this page24 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. What Is Destructuring?
  5. 2. Destructuring a Positional Record
  6. 3. Destructuring Named Fields
  7. 4. The Shape Must Match
  8. 5. Destructuring a List
  9. 6. When the List Length Doesn't Match
  10. 7. Rest Elements with ...
  11. 8. Skipping Values with _
  12. 9. final Destructuring
  13. 10. Swapping Two Variables
  14. 11. Destructuring in a for-in Loop
  15. 12. A Real-World Example
  16. Code Examples
  17. Common Beginner Mistakes
  18. Best Practices
  19. Mini Challenge
  20. Practice Questions
  21. Practice Answers
  22. Quick Revision
  23. Key Takeaways
  24. What's Next?

Lecture 78 — Destructuring

Category13 — Patterns & Modern Dart
Lecture078 of 140
Estimated video length10 min
TargetDart 3.12.2
LevelIntermediate

A record or a list holds several values, but reading them one by one with .$1, .name or [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-in loops, lists and indexes
  • looping over map.entries with entry.key and entry.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:

dart
(String, int) getStudent() => ('Ali', 20);

void main() {
  var student = getStudent();
  var name = student.$1;
  var age = student.$2;
  print('$name is $age');
}
output
Ali is 20

Three 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-in loop

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:

dart
(String, int) getStudent() => ('Ali', 20);

void main() {
  var (name, age) = getStudent();
  print('$name is $age');
}
output
Ali is 20

The 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":

dart
({String name, int age}) getStudent() => (name: 'Sara', age: 19);

void main() {
  var (:name, :age) = getStudent();
  print('$name is $age');
}
output
Sara is 19

Named 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:

dart
void main() {
  var (a, b) = ('Ali', 20, true);
  print(a);
}
output
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:

dart
void main() {
  var marks = [80, 75, 90];
  var [math, science, english] = marks;
  print('Math $math, Science $science, English $english');
}
output
Math 80, Science 75, English 90

math 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:

dart
void main() {
  var marks = [80, 75];
  var [math, science, english] = marks;
  print('$math $science $english');
}
output
Unhandled exception:
Bad state: Pattern matching error

The 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:

dart
void main() {
  var scores = [95, 88, 72, 64];
  var [top, ...others] = scores;
  print('Top: $top');
  print('Others: $others');
}
output
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:

dart
void main() {
  var temps = [21.5, 23.0, 25.5, 22.0];
  var [first, ..., last] = temps;
  print('First $first, last $last');
}
output
First 21.5, last 22.0

8. Skipping Values with _

When you don't need a value, write the wildcard _. It matches the value and throws it away:

dart
(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');
}
output
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:

dart
void main() {
  final (name, balance) = ('Ali', 1500.0);
  print('$name has $balance');
}
output
Ali has 1500.0

As 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:

dart
void main() {
  var a = 'Ali';
  var b = 'Sara';
  (a, b) = (b, a);
  print('a: $a, b: $b');
}
output
a: Sara, b: Ali

Dart 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:

dart
void main() {
  var students = [('Ali', 72), ('Sara', 95)];
  for (var (name, mark) in students) {
    print('$name: $mark');
  }
}
output
Ali: 72
Sara: 95

Lecture 050 promised a shorter way to loop over a map. Each entry is a MapEntry, and MapEntry(:key, :value) unpacks its two properties:

dart
void main() {
  var prices = {'Milk': 250, 'Bread': 120};
  for (var MapEntry(:key, :value) in prices.entries) {
    print('$key costs $value');
  }
}
output
Milk costs 250
Bread costs 120

Rename 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

dart
(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');
  }
}
output
Monday: 3 readings, high 24.5, change 3.0
Tuesday: 3 readings, high 26.0, change 5.0

A 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

dart
void main() {
  var (city, temp) = ('Lahore', 31);
  print('$city: $temp°C');
}
output
Lahore: 31°C

Example 2 — Named Record

dart
void main() {
  var (:name, :age) = (name: 'Zayan', age: 6);
  print('$name is $age');
}
output
Zayan is 6

Example 3 — Renaming Named Fields

dart
void main() {
  var (name: who, age: years) = (name: 'Sara', age: 19);
  print('$who, $years');
}
output
Sara, 19

Example 4 — List with a Rest Element

dart
void main() {
  var [first, second, ...rest] = [1, 2, 3, 4, 5];
  print('$first $second $rest');
}
output
1 2 [3, 4, 5]

Example 5 — Wildcards

dart
void main() {
  var [_, middle, _] = ['Ali', 'Sara', 'Zayan'];
  print(middle);
}
output
Sara

Example 6 — Swapping

dart
void main() {
  var left = 10;
  var right = 20;
  (left, right) = (right, left);
  print('$left $right');
}
output
20 10

Example 7 — Student Marks

dart
(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');
}
output
Total: 240
Average: 80.0

How it works

  1. summary returns two results as one record.
  2. final (total, average) unpacks them; total is an int, average a double.

Example 8 — Shopping Total

dart
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');
}
output
Notebook x3 = 1500
Pen x5 = 200
Total: 1700

How it works

  1. Each cart line is a three-field record.
  2. The loop unpacks every record into item, price and qty.

Example 9 — Bank Balance

dart
void main() {
  var balances = {'Ali': 1500.0, 'Sara': 2487.5};
  for (final MapEntry(key: owner, value: amount) in balances.entries) {
    print('$owner: $amount');
  }
}
output
Ali: 1500.0
Sara: 2487.5

How it works

  1. balances.entries gives one MapEntry per account.
  2. The pattern unpacks key into owner and value into amount.

Example 10 — Temperature Range

dart
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}');
}
output
Morning 22.0, night 24.5
Change: 2.5

How it works

  1. morning is the first reading and night the last; ... skips the middle.
  2. The list needs at least two elements, or the program crashes.

Common Beginner Mistakes

1. Unpacking a list of the wrong length

dart
void main() {
  var marks = [80, 75, 90];
  // var [a, b] = marks; // ✗ three elements, two variables
}
output
Unhandled exception:
Bad state: Pattern matching error

This only fails while running. Use var [a, b, ...] = marks; if extra elements are fine.

2. A record pattern of the wrong shape

dart
void main() {
  // var (a, b) = ('Ali', 20, true); // ✗
}
output
Error: The matched value of type '(String, int, bool)' isn't assignable to the required type '(Object?, Object?)'.

3. Misspelling a named field

dart
({String name, int age}) getStudent() => (name: 'Ali', age: 20);

void main() {
  // var (:name, :marks) = getStudent(); // ✗ no field called marks
}
output
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)

dart
void main() {
  // var (x, y) = [1, 2]; // ✗
}
output
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

dart
void main() {
  var [x, _, _] = [1, 2, 3];
  // print(_); // ✗
}
output
Error: Undefined name '_'.

6. Two rest elements

dart
void main() {
  // var [a, ...b, c, ...d] = [1, 2, 3, 4]; // ✗
}
output
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 .$1 and .$2.
  • Prefer named fields and :name when 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 final when the unpacked values should not change.
  • Loop over map.entries with MapEntry(: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:

output
Lowest: 72, highest: 95

Try it yourself before opening the solution.

Show solution
dart
(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');
}
output
Lowest: 72, highest: 95

How it works

  1. A sorted copy puts the lowest mark first and the highest last.
  2. [low, ..., high] takes both ends; the record carries them back to main.

Practice Questions

Work through these in order — they run from beginner to application to challenge.

Beginner

  1. Destructure ('Karachi', 34) into city and temp, and print Karachi: 34.
  2. Destructure (name: 'Ali', age: 20) with :name and :age, and print Ali, 20.
  3. Destructure [10, 20, 30] into three variables and print their sum.

Application

  1. From ['Sara', 'Ali', 'Zayan', 'Omar'], take the first name and put the rest in a list. Print Sara and [Ali, Zayan, Omar].
  2. From the record ('Ali', 20, 'Lahore'), print only Ali from Lahore, using _.
  3. Start with var x = 3; and var y = 7;. Swap them in one line and print 7 3.
  4. Loop over {'Pen': 40, 'Bag': 1500} with MapEntry(:key, :value) and print each line as Pen: 40.

Challenge

  1. Write split(double bill) that returns (tip, total) for a 10% tip. Destructure split(2000) with final and print Tip: 200.0, total: 2200.0.

  2. Loop over [('Ali', 72, 80), ('Sara', 95, 91)] (name, math, science), destructuring each record, and print Ali: 152 and Sara: 186.

  3. This program crashes. Explain why in one sentence, then fix it with a rest element so it prints 95 64:

    dart
    void main() {
      var scores = [95, 88, 72, 64];
      var [first, last] = scores;
      print('$first $last');
    }

Practice Answers

Question 1 — Solution

dart
void main() {
  var (city, temp) = ('Karachi', 34);
  print('$city: $temp');
}
output
Karachi: 34

Question 2 — Solution

dart
void main() {
  var (:name, :age) = (name: 'Ali', age: 20);
  print('$name, $age');
}
output
Ali, 20

Question 3 — Solution

dart
void main() {
  var [a, b, c] = [10, 20, 30];
  print(a + b + c);
}
output
60

Question 4 — Solution

dart
void main() {
  var [first, ...rest] = ['Sara', 'Ali', 'Zayan', 'Omar'];
  print(first);
  print(rest);
}
output
Sara
[Ali, Zayan, Omar]

Question 5 — Solution

dart
void main() {
  var (name, _, city) = ('Ali', 20, 'Lahore');
  print('$name from $city');
}
output
Ali from Lahore

Question 6 — Solution

dart
void main() {
  var x = 3;
  var y = 7;
  (x, y) = (y, x);
  print('$x $y');
}
output
7 3

Question 7 — Solution

dart
void main() {
  var stock = {'Pen': 40, 'Bag': 1500};
  for (var MapEntry(:key, :value) in stock.entries) {
    print('$key: $value');
  }
}
output
Pen: 40
Bag: 1500

Question 8 — Solution

dart
(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');
}
output
Tip: 200.0, total: 2200.0

Question 9 — Solution

dart
void main() {
  var results = [('Ali', 72, 80), ('Sara', 95, 91)];
  for (var (name, math, science) in results) {
    print('$name: ${math + science}');
  }
}
output
Ali: 152
Sara: 186

Question 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:

dart
void main() {
  var scores = [95, 88, 72, 64];
  var [first, ..., last] = scores;
  print('$first $last');
}
output
95 64

Quick Revision

  1. What does destructuring do?
  2. Which brackets unpack a record, and which a list?
  3. What does :name mean in var (:name, :age) = ...?
  4. Why is a wrong record shape a compile error, but a wrong list length a runtime crash?
  5. What does ...rest collect?
  6. Can you read the value matched by _?
  7. How do you swap a and b without a temporary variable?
  8. 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.
  • ...rest collects 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.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.