| |

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

PatternMatches
[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

PatternMatches
{'key': v}Map with key
{'a': a, 'b': b}Map with both keys
{'key': v, ...rest}Map with key, rest captured

Record Patterns

PatternMatches
(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

ContextExample
Declarationvar [a, b] = list;
Assignment(a, b) = (1, 2);
Switch statementcase [a, b]:
Switch expression[a, b] => a + b
If-caseif (value case [a, b])
For loopfor (var (a, b) in pairs)
While loopwhile (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

PitfallWhy It HappensFix
List length mismatchPattern expects exactUse rest or match length
Map key not literalVariable in patternUse a literal
Variable already boundSame name twiceRename
Pattern not matchingWrong shapeCheck the shape
Rest is a new listCopy, not viewExpected behavior
Record field mismatchWrong arityMatch 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

ItemValue
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_
ContextsDeclaration, assignment, switch, for, if-case
NestedAny pattern inside another
ExhaustivenessChecked 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 ...rest captures 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 :x shorthand is the abbreviation for x: 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!