Dart 33 🎯 Pattern Matching in Assignments, Destructuring Lists, Maps, and Records
A pattern is a description of a value’s shape. Pattern matching is the operation of checking whether a value has that shape and extracting the parts. Dart 3 introduced patterns as a first-class feature, and they appear in four places: variable declarations, assignments, switch statements and expressions, and for loops. The patterns destructure lists, maps, records, objects, and the rest of the language’s types, and they can be nested arbitrarily.
This chapter covers pattern matching in assignments and destructuring. You will learn the patterns for lists, maps, and records, the rest pattern that captures the remaining elements, the wildcard that ignores a value, the variable pattern that binds a value, and the patterns that combine to describe a complex shape.
Key point: A pattern is matched against a value. If the value has the shape the pattern describes, the match succeeds and the pattern’s variables are bound. The patterns for lists use brackets [...], for maps use braces with keys {'key': pattern}, and for records use parentheses (...). The rest pattern ... captures the remaining elements, and the wildcard _ ignores a value. Patterns can be nested, and they can be used in declarations, assignments, switches, and loops.
Why pattern matching matters
The destructuring problem. A value that is a list, a map, or a record has parts. Extracting the parts with index access or key lookup is verbose. A pattern extracts them in one declaration: var [a, b] = list is shorter than var a = list[0]; var b = list[1];. The pattern is the description of the shape and the extraction in one expression.
The type-check problem. A pattern can check the type of the value as part of the match. if (value case int n) checks that the value is an int and binds it to n. The type check and the extraction are combined.
The exhaustiveness problem. A switch over a sealed class or an enum can be checked for exhaustiveness. The patterns must cover every possible subtype or value. The compiler verifies it, and adding a new subtype turns every incomplete switch into a compile error.
The readability problem. A switch with patterns reads as a description of the cases. The pattern is the case, and the body is the action. The structure is visible in the code, and the reader can see which shapes are handled.
The safety problem. A pattern that does not match does not bind. A variable bound inside a pattern is only in scope where the pattern matched. The scope is the safety: the variable cannot be used where the value’s shape is not known.
The composition problem. Patterns compose. A list pattern can contain a record pattern, which can contain a map pattern. The nesting describes a complex shape with the same syntax as the simple ones.
a. List and map patterns
A list pattern matches a list with a specific number of elements. The pattern uses brackets.
var [a, b, c] = [1, 2, 3];
print(a); // 1
print(b); // 2
print(c); // 3
The pattern [a, b, c] matches a list with exactly three elements. The variables a, b, and c are bound to the elements. If the list does not have three elements, the match fails with an error in a declaration.
A list pattern can have a rest pattern that captures the remaining elements.
var [first, ...rest] = [1, 2, 3, 4, 5];
print(first); // 1
print(rest); // [2, 3, 4, 5]
The ...rest captures the elements after the first. The rest is a List<int>.
The rest pattern can be in the middle.
var [first, ..., last] = [1, 2, 3, 4, 5];
print(first); // 1
print(last); // 5
The ... in the middle ignores the elements between the first and the last. The ... with no name is the wildcard rest, which ignores the elements.
A map pattern matches a map with specific keys. The pattern uses braces with the keys.
var {'name': name, 'age': age} = {'name': 'Alice', 'age': 30};
print(name); // 'Alice'
print(age); // 30
The pattern matches a map with the keys 'name' and 'age'. The variables are bound to the values. A map pattern does not require the map to have exactly the keys; it requires the keys to be present. Additional keys are ignored.
var {'name': name} = {'name': 'Alice', 'age': 30};
print(name); // 'Alice'
The pattern matches even though the map has an extra key. The age key is ignored.
The key in a map pattern must be a literal or a constant. A variable cannot be used as a key in a pattern.
const key = 'name';
var {key: value} = {'name': 'Alice'}; // error: key is not a literal
The key in the pattern is interpreted as the literal string 'key', not the variable. The match fails because the map does not have a 'key' key.
The rest pattern in a map captures the remaining entries.
var {'name': name, ...rest} = {'name': 'Alice', 'age': 30, 'city': 'NYC'};
print(name); // 'Alice'
print(rest); // {'age': 30, 'city': 'NYC'}
The ...rest captures the entries that are not matched by the named keys.
b. Record patterns
A record pattern matches a record with a specific shape. The pattern uses parentheses.
var (a, b) = (1, 2);
print(a); // 1
print(b); // 2
The pattern (a, b) matches a record with two positional fields. The variables are bound to the fields.
A named field is matched with the name: pattern syntax.
var (x: x, y: y) = (x: 1, y: 2);
print(x); // 1
print(y); // 2
The x: x matches the named field x and binds it to the variable x. The y: y does the same for y.
The :pattern shorthand infers the name from the variable.
var (:x, :y) = (x: 1, y: 2);
print(x); // 1
print(y); // 2
The :x is the shorthand for x: x. The variable name and the field name are the same.
A record pattern can combine positional and named fields.
var (a, b, c: c) = (1, 2, c: 3);
print(a); // 1
print(b); // 2
print(c); // 3
The positional fields a and b come first, and the named field c is matched with c: c.
A record pattern can be nested inside another pattern.
var (a, (b, c)) = (1, (2, 3));
print(a); // 1
print(b); // 2
print(c); // 3
The outer pattern matches the record with two fields, and the second field is itself a record matched by the nested pattern (b, c).
A record pattern can use the wildcard _ to ignore a field.
var (a, _) = (1, 2);
print(a); // 1
The _ ignores the second field. The wildcard does not bind a variable.
A record pattern can use the rest pattern.
var (a, ...) = (1, 2, 3);
print(a); // 1
The ... ignores the remaining positional fields. The rest pattern in a record does not bind a variable because the record’s fields are not a collection.
c. Patterns in assignments, switches, and loops
A pattern can be used in an assignment. The pattern is on the left, and the value is on the right. The variables must already be declared.
var a, b;
(a, b) = (1, 2);
print(a); // 1
print(b); // 2
The (a, b) = (1, 2) assigns the record’s fields to the variables a and b. The variables are declared before the assignment.
A pattern can be used in a switch statement.
switch (value) {
case [int a, int b]:
print('Two ints: $a, $b');
case {'name': String name}:
print('Map with name: $name');
case (int a, int b):
print('Record: $a, $b');
default:
print('Unknown');
}
The case [int a, int b] matches a list of two integers. The case {'name': String name} matches a map with a 'name' key whose value is a String. The case (int a, int b) matches a record of two integers.
A pattern can be used in a switch expression.
var result = switch (value) {
[int a, int b] => a + b,
{'name': String name} => name,
(int a, int b) => a * b,
_ => 0,
};
The switch expression returns a value from each case. The _ is the wildcard, which matches anything and provides the default.
A pattern can be used in a for loop.
var pairs = [(1, 'a'), (2, 'b'), (3, 'c')];
for (var (number, letter) in pairs) {
print('$number = $letter');
}
The var (number, letter) destructures each record in the list. The loop body uses the destructured variables.
A pattern can be used in an if-case.
if (value case [int a, int b]) {
print(a + b);
}
The if-case is the pattern-matching conditional. The pattern matches the value, and the body runs if the match succeeds.
A pattern can be used in a while loop.
while (list case [_, ...rest]) {
list = rest;
// process
}
The while loop continues while the list has at least one element. The pattern [_, ...rest] matches a non-empty list and captures the rest. The loop removes the first element each iteration.
Complete Example Session
// ============================================
// PART 1: LIST PATTERN
// ============================================
var [a, b, c] = [1, 2, 3];
print('$a $b $c'); // 1 2 3
// ============================================
// PART 2: LIST WITH REST
// ============================================
var [first, ...rest] = [1, 2, 3, 4];
print(first); // 1
print(rest); // [2, 3, 4]
// ============================================
// PART 3: MAP PATTERN
// ============================================
var {'name': name, 'age': age} = {'name': 'Alice', 'age': 30};
print('$name is $age'); // Alice is 30
// ============================================
// PART 4: MAP WITH REST
// ============================================
var {'name': name, ...rest} = {'name': 'Alice', 'age': 30, 'city': 'NYC'};
print(name); // 'Alice'
print(rest); // {age: 30, city: NYC}
// ============================================
// PART 5: RECORD PATTERN
// ============================================
var (a, b) = (1, 2);
print('$a $b'); // 1 2
// ============================================
// PART 6: RECORD WITH NAMED FIELDS
// ============================================
var (x: x, y: y) = (x: 1, y: 2);
print('$x $y'); // 1 2
var (:x, :y) = (x: 1, y: 2); // shorthand
// ============================================
// PART 7: NESTED PATTERN
// ============================================
var (a, (b, c)) = (1, (2, 3));
print('$a $b $c'); // 1 2 3
// ============================================
// PART 8: WILDCARD AND REST
// ============================================
var (a, _) = (1, 2);
print(a); // 1
var [first, ..., last] = [1, 2, 3, 4, 5];
print('$first $last'); // 1 5
// ============================================
// PART 9: SWITCH PATTERN
// ============================================
switch (value) {
case [int a, int b]:
print(a + b);
case {'name': String name}:
print(name);
case (int a, int b):
print(a * b);
default:
print('Unknown');
}
// ============================================
// PART 10: FOR LOOP PATTERN
// ============================================
var pairs = [(1, 'a'), (2, 'b'), (3, 'c')];
for (var (number, letter) in pairs) {
print('$number = $letter');
}
The ten parts covered the list pattern, the list with rest, the map pattern, the map with rest, the record pattern, the record with named fields, the nested pattern, the wildcard and rest, the switch pattern, and the for loop pattern.
Quick Reference
List Patterns
| Pattern | Matches |
|---|---|
[a, b] | List of exactly 2 |
[a, ...] | List with at least 1 |
[a, ...rest] | List with at least 1, rest captured |
[_, b] | Second element, first ignored |
Map Patterns
| Pattern | Matches |
|---|---|
{'key': v} | Map with key |
{'a': a, 'b': b} | Map with both keys |
{'key': v, ...rest} | Map with key, rest captured |
Record Patterns
| Pattern | Matches |
|---|---|
(a, b) | Record with 2 positional |
(x: a, y: b) | Record with named x and y |
(:x, :y) | Shorthand for x: x, y: y |
(a, _) | First field, second ignored |
(a, ...) | First field, rest ignored |
(a, (b, c)) | Nested record |
Pattern Contexts
| Context | Example |
|---|---|
| Declaration | var [a, b] = list; |
| Assignment | (a, b) = (1, 2); |
| Switch statement | case [a, b]: |
| Switch expression | [a, b] => a + b |
| If-case | if (value case [a, b]) |
| For loop | for (var (a, b) in pairs) |
| While loop | while (list case [_, ...rest]) |
Best Practices
✅ Do This:
// Use list patterns for fixed-length lists
var [a, b] = [1, 2]; // ✅
// Use rest for the remainder
var [first, ...rest] = list; // ✅
// Use map patterns for keyed data
var {'name': name} = json; // ✅
// Use record patterns for records
var (x, y) = point; // ✅
// Use the shorthand for named fields
var (:x, :y) = (x: 1, y: 2); // ✅
// Use wildcards to ignore
var (a, _) = record; // ✅
// Use patterns in switch for shape matching
switch (value) {
case [int a, int b]: // ...
} // ✅
❌ Don’t Do This:
// Don't use a variable as a map pattern key
var {key: value} = map; // key is the literal 'key' // ❌
// Don't expect a list pattern to match a different length
var [a, b] = [1, 2, 3]; // error // ❌
// Don't forget the rest pattern captures a new collection
var [first, ...rest] = list; // rest is a new list // ✅
// Don't use a pattern where a value is expected
var x = [a, b]; // this is a list, not a pattern // ⚠️
// Don't use the same variable twice in a pattern
var (a, a) = (1, 2); // error // ❌
// Don't forget the wildcard for the ignored field
var (a, b) = (1, 2, 3); // error, too many fields // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| List length mismatch | Pattern expects exact | Use rest or match length |
| Map key not literal | Variable in pattern | Use a literal |
| Variable already bound | Same name twice | Rename |
| Pattern not matching | Wrong shape | Check the shape |
| Rest is a new list | Copy, not view | Expected behavior |
| Record field mismatch | Wrong arity | Match the fields |
Real-World Examples
1. List Destructuring
var [first, second] = [1, 2];
2. List with Rest
var [head, ...tail] = [1, 2, 3, 4];
3. Map Destructuring
var {'name': name, 'age': age} = user;
4. Record Destructuring
var (x, y) = point;
5. Named Record
var (:name, :age) = user;
6. Nested Record
var (a, (b, c)) = (1, (2, 3));
7. Switch on List
switch (value) {
case [int a, int b]: print(a + b);
case [int a]: print(a);
default: print('other');
}
8. Switch on Map
switch (json) {
case {'type': 'user', 'name': String name}: print(name);
case {'type': 'error', 'message': String msg}: print(msg);
default: print('unknown');
}
9. For Loop Pattern
for (var (key, value) in map.entries) {
print('$key: $value');
}
10. If-Case
if (value case [int a, int b]) {
print(a + b);
}
Visual
List Pattern
┌─────────────────────────────────────────────────────────────┐
│ var [a, b, c] = [1, 2, 3]; │
│ │
│ Pattern: [a, b, c] │
│ Value: [1, 2, 3] │
│ │
│ a = 1 │
│ b = 2 │
│ c = 3 │
│ │
│ The pattern matches the length and binds each element. │
│ │
└─────────────────────────────────────────────────────────────┘
Rest Pattern
┌─────────────────────────────────────────────────────────────┐
│ var [first, ...rest] = [1, 2, 3, 4]; │
│ │
│ first = 1 │
│ rest = [2, 3, 4] │
│ │
│ The rest captures the remaining elements as a new list. │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ var [first, ..., last] = [1, 2, 3, 4]; │
│ │
│ first = 1 │
│ last = 4 │
│ │
│ The middle elements are ignored. │
│ │
└─────────────────────────────────────────────────────────────┘
Map Pattern
┌─────────────────────────────────────────────────────────────┐
│ var {'name': name, 'age': age} = {'name': 'Alice', ...}; │
│ │
│ Pattern: {'name': name, 'age': age} │
│ Value: {'name': 'Alice', 'age': 30, 'city': 'NYC'} │
│ │
│ name = 'Alice' │
│ age = 30 │
│ 'city' is ignored │
│ │
│ The pattern requires the keys to be present, not exact. │
│ │
└─────────────────────────────────────────────────────────────┘
Record Pattern
┌─────────────────────────────────────────────────────────────┐
│ var (a, b) = (1, 2); │
│ │
│ Pattern: (a, b) │
│ Value: (1, 2) │
│ │
│ a = 1 │
│ b = 2 │
│ │
│ The pattern matches the record's shape. │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| List pattern | [a, b] |
| List rest | [a, ...rest] |
| List middle rest | [a, ..., b] |
| Map pattern | {'key': value} |
| Map rest | {'key': v, ...rest} |
| Record pattern | (a, b) |
| Record named | (x: a, y: b) |
| Record shorthand | (:x, :y) |
| Wildcard | _ |
| Contexts | Declaration, assignment, switch, for, if-case |
| Nested | Any pattern inside another |
| Exhaustiveness | Checked in switch |
Key takeaways:
- A pattern describes the shape of a value. The pattern matches if the value has that shape, and the pattern’s variables are bound to the parts. The patterns for lists use brackets, for maps use braces with keys, and for records use parentheses.
- List patterns match a specific length.
[a, b, c]matches a list with exactly three elements. The rest pattern...restcaptures the remaining elements as a new list. The...without a name ignores them. - Map patterns match specific keys.
{'name': name}matches a map that has a'name'key. The pattern does not require the map to have exactly those keys; additional keys are ignored. The key must be a literal or a constant. - Record patterns match the shape.
(a, b)matches a record with two positional fields.(x: a, y: b)matches the named fields. The:xshorthand is the abbreviation forx: x. - Patterns compose. A list pattern can contain a record pattern, which can contain a map pattern. The nesting describes a complex shape with the same syntax as the simple ones.
- Patterns are used in six contexts. Variable declarations, assignments, switch statements, switch expressions, if-case statements, and for/while loops. The pattern is the same in every context.
- The wildcard
_ignores a value. It does not bind a variable. It is used for the fields that the pattern does not need.
Remember: Patterns are the mechanism for describing the shape of a value and extracting the parts. They are used in declarations, assignments, switches, and loops. The patterns for lists, maps, and records are the ones that appear most often, and they compose with each other. Use patterns for destructuring, for type-checking in a switch, and for the data that flows through the application. The pattern is the description, the match is the check, and the binding is the extraction. And remember the exhaustiveness: a switch over a sealed type must handle every case, and the compiler is the checker.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!