# Pattern Matching

Course: [Dart](https://www.devtutorials.ai/learn/dart) · Section: Pattern Matching
Source: https://www.devtutorials.ai/learn/dart/records-and-patterns/pattern-matching

> How to bundle values with records, and how patterns check the shape of a value and take it apart: in declarations, switch, when guards and if-case.

## What Are Records?

**Record** = a small, fixed bundle of values, written in round brackets: `('Asha', 92)`.

- It can hold values of **different types** together.
- You do **not** write a class for it.
- Its size is **fixed**. You cannot add or remove values later.
- Its values **cannot be changed** after it is made.

**Simple lesson:** a record is a quick way to keep a few values together.

### A Real-World Example

Think of a **tiffin box** with compartments.

- One part has rice, one has dal, one has a sweet.
- They travel together, in one box.
- You would not build a cupboard (a class) just to carry lunch.
- A record is the tiffin box: `('rice', 'dal', sweet: 'ladoo')`.

Every **Run** box on this page runs on the course's Dart runner in your browser.

Records need **Dart 3** or newer. Check with `dart --version` on your computer.

## What You Will Learn

In this lesson, you will learn:

1. How to create records with positional and named fields.
2. How to return several values, and how records are compared.
3. Basic patterns: variables, constants, wildcards and comparisons.
4. Record, object, list and map patterns.
5. Patterns in switch, `when` guards, sealed classes and if-case.

## Creating Records

### Positional Fields

**Positional field** = a field known by its **position**: first, second, third.

```dart runnable title="A record with three positional fields"
void main() {
  var student = ('Asha', 13, 'Blue');
  print(student);
  print(student.$1);
  print(student.$2);
  print(student.$3);
}
```

- Read them with **`$1`**, **`$2`**, **`$3`**.
- Counting starts at **1** here, not 0 like lists.

### Named Fields

**Named field** = a field with a **name**, written `name: value`.

```dart runnable title="Named fields are easier to read"
void main() {
  var exam = (marks: 92, subject: 'Maths');
  print(exam.subject);
  print(exam.marks);
  print(exam);
}
```

### Mixing Both

Click a field to see how it is read:

```dart runnable title="Positional and named together"
void main() {
  var r = ('Asha', 92, age: 13, house: 'Blue');
  print(r.$1);
  print(r.$2);
  print(r.age);
  print(r.house);
}
```

- Named fields do **not** count for `$1`, `$2`. Only positional ones do.
- A record with one positional field needs a comma: `('Asha',)`.

Records are **read-only**. `r.$1 = 'Ravi';` is an error before running.

To "change" a record, make a new one: `r = ('Ravi', r.$2);`

**Simple lesson:** read positional fields by number with `$1`, and named fields by their name.

## Returning Multiple Values

Without records, a function can return only **one** value. With records, it can return a bundle:

```dart runnable title="Lowest and highest marks in one go"
(int, int) minMax(List<int> marks) {
  var low = marks[0];
  var high = marks[0];
  for (var m in marks) {
    if (m < low) low = m;
    if (m > high) high = m;
  }
  return (low, high);
}

void main() {
  var result = minMax([72, 95, 64, 88]);
  print('Lowest: ${result.$1}');
  print('Highest: ${result.$2}');
}
```

You can unpack the record straight into variables. This is called **destructuring**:

Named fields unpack by name with `:name`:

```dart runnable title="Unpacking named fields"
({int present, int absent}) attendance(List<bool> days) {
  var p = days.where((d) => d).length;
  return (present: p, absent: days.length - p);
}

void main() {
  var (:present, :absent) = attendance([true, true, false, true, true]);
  print('Present: $present');
  print('Absent: $absent');
}
```

You will learn every kind of unpacking below.

**Simple lesson:** return a record when a function has more than one answer.

## Record Types and Equality

### Record Types

**Record type** = the shape of a record: its field types, and the names of named fields.

| Record | Its type |
|---|---|
| `('Asha', 13)` | `(String, int)` |
| `(marks: 92, subject: 'Maths')` | `({int marks, String subject})` |
| `('Asha', age: 13)` | `(String, {int age})` |

```dart runnable title="Using a record type"
void main() {
  (String, int) topper = ('Priya', 98);
  ({double h, double w}) size = (h: 1.5, w: 2.5);
  print(topper.runtimeType);
  print(size.runtimeType);
  print(size.w * size.h);
}
```

### Equality

Two records are **equal** when they have the **same shape** and **equal values**:

```dart runnable title="Comparing records"
void main() {
  var a = ('Asha', 92);
  var b = ('Asha', 92);
  var c = ('Asha', 95);
  print(a == b);
  print(a == c);
  print((x: 1, y: 2) == (y: 2, x: 1));
}
```

- `a == b` is `true`, even though they are two different bundles.
- The order of **named** fields does not matter. `(x: 1, y: 2)` equals `(y: 2, x: 1)`.

Use a **record** for a quick bundle that a function returns or a loop uses.

Use a **class** when the data needs methods, rules, or a name you will use all over the app.

**Easy way to remember**

- `( , )` = a record. `$1, $2` = positional. `name:` = named.
- Return `(a, b)`, unpack with `var (a, b) = ...`.
- Same shape + same values = equal.

**Simple lesson:** a record's type is its shape, and records with the same shape and values are equal.

## What Is Pattern Matching?

**Pattern** = a description of what a value should **look like**. Dart compares the value with it.

**Pattern matching** does two jobs in one step:

- **Match:** does this value have the right shape? (A list of 2 items? A Student? A number above 90?)
- **Destructure:** if it does, take out the parts into new variables.

**Simple lesson:** a pattern asks "does it look like this?" and, if yes, "give me its parts".

### A Real-World Example

The school office sorts the morning post.

- "If it is a **parcel** for a **teacher**, send it to the staff room."
- "If it is a **letter** with **Exam** on it, send it to the exam cell."
- "If it is anything else, keep it at the front desk."
- The clerk looks at the **shape** of each item, then acts. That is pattern matching.

Pick a point and see which pattern catches it:

The rest of this lesson uses records from above and sealed classes from **Class Modifiers**.

## Destructuring

**Destructuring** = taking a value apart into separate variables, in one line.

```dart runnable title="Destructuring a record and a list"
void main() {
  var (name, marks) = ('Asha', 92);
  print('$name scored $marks');

  var [first, second, third] = ['Gold', 'Silver', 'Bronze'];
  print('First prize: $first');
  print('Third prize: $third');
}
```

- The left side is a **pattern**. Its shape must match the value.
- If the shapes do not match, Dart reports an error: `var [a, b] = [1, 2, 3];` fails, because there are 3 items, not 2.

**Simple lesson:** put a pattern on the left of `=`, and Dart fills its variables from the value on the right.

## Basic Patterns

### Variable, Constant and Wildcard

| Pattern | Looks like | Matches | Example |
|---|---|---|---|
| **Variable** | `var x`, `int x` | any value (of that type), and saves it | `var marks` |
| **Constant** | `0`, `'A'`, `null`, `Day.monday` | only that exact value | `0` |
| **Wildcard** | `_` | any value, and throws it away | `_` |
| **Relational** | `>= 90`, `< 0` | values that pass the comparison | `>= 90` |

```dart runnable title="Using _ to skip a part"
void main() {
  var (name, _, house) = ('Ravi', 14, 'Green');
  print('$name is in $house house');
}
```

### Combining with && and ||

```dart runnable title="Patterns joined with || and &&"
String band(int marks) => switch (marks) {
  >= 90 => 'Excellent',
  >= 75 && < 90 => 'Good',
  35 || 36 || 37 => 'Just passed',
  >= 38 => 'Pass',
  _ => 'Needs help',
};

void main() {
  for (var m in [95, 80, 36, 50, 12]) {
    print('$m: ${band(m)}');
  }
}
```

- `||` means "either pattern matches".
- `&&` means "both patterns match".

**Simple lesson:** small patterns (values, comparisons, `_`) can be joined to describe exactly what you want.

## Structure Patterns

### Record Patterns

```dart runnable title="Matching the shape of a point"
String where((int, int) point) => switch (point) {
  (0, 0) => 'At the centre',
  (0, var y) => 'On the vertical line, y = $y',
  (var x, 0) => 'On the horizontal line, x = $x',
  (var x, var y) => 'At ($x, $y)',
};

void main() {
  print(where((0, 0)));
  print(where((0, 7)));
  print(where((3, 0)));
  print(where((2, 5)));
}
```

### Object Patterns

**Object pattern** = `ClassName(field: pattern)`. It checks the type, then reads named fields (or getters).

```dart runnable title="Matching objects by type and field"
class Student {
  final String name;
  final int marks;
  Student(this.name, this.marks);
}

void main() {
  var s = Student('Priya', 96);
  var Student(:name, :marks) = s;
  print('$name: $marks');

  var message = switch (s) {
    Student(marks: >= 90, :var name) => '$name gets a gold star',
    Student(:var name) => '$name, keep going',
  };
  print(message);
}
```

- `:name` is short for `name: var name`.
- `marks: >= 90` puts a relational pattern on one field.

### List Patterns and Rest

**Rest element** = `...` inside a list pattern. It matches **any number** of items.

```dart runnable title="First, last and the rest"
void main() {
  var queue = ['Asha', 'Ravi', 'Priya', 'Imran', 'Meena'];

  var [first, ...others] = queue;
  print('First: $first, then ${others.length} more');

  var [head, ..., tail] = queue;
  print('$head is first, $tail is last');

  var [a, b, ...rest] = queue;
  print(rest);
}
```

### Map Patterns

**Map pattern** = `{'key': pattern}`. It checks that the key is there, then matches its value.

```dart runnable title="Pulling values out of a map"
void main() {
  var data = {'name': 'Kiran', 'class': 7, 'house': 'Red'};
  switch (data) {
    case {'name': String name, 'class': int grade}:
      print('$name is in class $grade');
    default:
      print('Not a student record');
  }
}
```

- A map pattern ignores keys you did not mention, like `'house'`.
- `String name` also checks the **type** of the value.
- A key may be missing, so map patterns are used where a match can fail: in `switch` or `if-case` (below).

**Simple lesson:** structure patterns match records, objects, lists and maps, and take out the parts you name.

## Patterns in switch

### Switch Statements and Expressions

You met `switch` in **Conditional Statements**. Every `case` can hold a full pattern:

```dart runnable title="A switch statement with patterns"
void describe(Object value) {
  switch (value) {
    case int n when n < 0:
      print('$n is a negative number');
    case int n:
      print('$n is a whole number');
    case String s when s.isEmpty:
      print('An empty text');
    case String s:
      print('Text with ${s.length} letters');
    case [var a, var b]:
      print('A pair: $a and $b');
    default:
      print('Something else');
  }
}

void main() {
  describe(-4);
  describe(12);
  describe('');
  describe('Dart');
  describe([1, 2]);
  describe(3.5);
}
```

### Guards with when

**Guard** = `when condition` after a pattern. The case matches only if the pattern matches **and** the condition is `true`.

### Exhaustiveness with Sealed Classes

With a `sealed` parent, Dart knows every child, so a switch can cover them all **without** `_`:

```dart runnable title="Handling every kind of API reply"
sealed class Reply {}

class Success extends Reply {
  final List<String> names;
  Success(this.names);
}

class NotFound extends Reply {}

class Failure extends Reply {
  final int code;
  Failure(this.code);
}

String show(Reply r) => switch (r) {
  Success(names: []) => 'No students yet',
  Success(:var names) => 'Students: ${names.join(', ')}',
  NotFound() => 'This class does not exist',
  Failure(code: >= 500) => 'Server problem, try later',
  Failure(:var code) => 'Error $code',
};

void main() {
  print(show(Success(['Asha', 'Ravi'])));
  print(show(Success([])));
  print(show(NotFound()));
  print(show(Failure(503)));
  print(show(Failure(401)));
}
```

The **first** matching case wins. Put **specific** cases (with guards or exact values) **before** general ones.

If `Failure(:var code)` came first, `Failure(code: >= 500)` would never be reached. Real Dart warns about such unreachable cases.

**Simple lesson:** in a switch, patterns are tried from top to bottom, and `when` adds an extra condition.

## if-case

**if-case** = `if (value case pattern)`. It runs the block only if the value matches. The pattern's variables can be used inside.

```dart runnable title="Checking data before using it"
void main() {
  var reply = {'status': 'ok', 'marks': [88, 92, 79]};

  if (reply case {'status': 'ok', 'marks': [var first, ...]}) {
    print('First mark is $first');
  } else {
    print('Bad reply');
  }

  var broken = {'status': 'ok', 'marks': 'none'};
  if (broken case {'marks': List<int> m}) {
    print('Got ${m.length} marks');
  } else {
    print('marks is missing or not a list');
  }
}
```

- One line checks the key, the type, and the list shape.
- No crash if the data is wrong. You just take the `else` path.

**Easy way to remember**

- `var (a, b) = ...` = **take apart** (must match).
- `switch` + patterns = **choose** by shape, top to bottom.
- `when` = **extra condition**.
- `if (x case ...)` = **check and take apart**, safely.

**Simple lesson:** `if-case` checks the shape of data and gives you its parts, all in one condition.

## TL;DR

1. **Record** = a fixed bundle: `('Asha', age: 13)`. Read with `$1` and `.age`.
2. Functions can **return** a record. Same shape and values means `==` is `true`.
3. A **pattern** checks the shape of a value and takes out its parts: `var (a, b) = r;`
4. **Structure patterns:** `(a, b)`, `Student(:name)`, `[x, ...rest]`, `{'k': v}`.
5. **switch** tries patterns top to bottom, **`when`** adds a guard, and **`if (x case ...)`** checks data safely.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| `r.$0` for the first field | Counting starts at 0 | Record fields start at `$1` | A compile error. |
| `r.$1 = 5;` | Records can change | Records are read-only | A compile error. |
| `('Asha')` for a one-field record | Brackets make a record | Add a comma: `('Asha',)` | It is just a String in brackets. |
| Counting named fields in `$n` | All fields are numbered | Only positional fields get `$n` | `$3` does not exist. |
| A record for big app data | Records replace classes | Use a class with methods and rules | Hard-to-read code. |
| A general case before a specific one | Dart picks the best match | Dart picks the **first** match | The specific case never runs. |
| `var [a, b] = [1, 2, 3];` | Extra items are ignored | List patterns must match the length, or use `...` | An error: the pattern does not match. |
| Using `case x:` to save a value | A bare name saves the value | In `case`, write `var x` to save it | It compares with a constant named `x`. |
| `_` in the middle of a sealed switch | It is harmless | Cases after `_` are never reached | Unreachable code, and lost exhaustiveness. |
| Reading `json['marks'][0]` without checks | The data is always right | Use `if-case` to check first | A crash on bad data. |

## Check Yourself

## Exercises

## What to Remember

- A record is a fixed, read-only bundle of values.
- Return a record when a function has more than one answer.
- A pattern checks the shape of a value and gives you its parts.
- In switch, the first match wins, and `when` adds a condition.
- Use `if-case` to check outside data before you use it.

Next: **Exception Handling**. You will learn to catch errors and keep your program running.
