# Pattern Matching

Course: [Python](https://www.devtutorials.ai/learn/python) · Section: Control Flow
Source: https://www.devtutorials.ai/learn/python/control-flow/pattern-matching

> The match statement: cases, the | pattern for grouping values, case _ as the default, the bare-name trap, and when match beats an if chain.

Imagine sorting the post at a school office.

You pick up each envelope and look at it. A bill goes to accounts. A letter from a parent goes to the class teacher. A parcel goes to the store room. Anything else goes in the tray to deal with later.

You are not asking one yes-or-no question. You are looking at the shape of the thing in your hand and matching it to a case you already know.

Python can do the same with `match`.

```python runnable title="Sorting one value into cases"
command = "report"

match command:
    case "report":
        print("Printing the report card")
    case "attendance":
        print("Showing the attendance sheet")
    case _:
        print("Unknown command")
```

Python took the value of `command` and tried each `case` from the top. The first one that matched ran, and the rest were skipped.

## What You Will Learn

In this lesson, you will learn:

- What pattern matching is, and what it is for
- How to write a `match` statement and its `case` clauses
- How to match plain values such as numbers and text
- How to let one `case` cover several values with `|`
- How to catch everything that did not match, using `case _`
- When `match` reads better than `if elif else`, and when it does not

`match` compares one value against several patterns and runs the first one that fits.

## Pattern Matching

### What Is Pattern Matching?

**Pattern matching** means comparing a value against several shapes, called **patterns**, and acting on the first one that fits.

A **pattern** is the value or shape a `case` is written to accept — `35`, `"report"`, or a pair like `("maths", 78)` are all patterns.

It arrived in Python 3.10. Code written for older versions does not have it, so you will still meet plenty of `if elif else` chains doing the same job.

### Why Pattern Matching Is Used

When a program has to react to one value that could be several different things, an `if elif else` chain repeats the variable name on every line.

```python runnable title="The same name on every line"
command = "attendance"

if command == "report":
    print("Printing the report card")
elif command == "attendance":
    print("Showing the attendance sheet")
elif command == "marks":
    print("Showing the mark sheet")
else:
    print("Unknown command")
```

`command ==` appears three times. It is easy to misspell one, and easy to compare the wrong variable by mistake.

`match` names the value once, at the top, and then each `case` just states what it is looking for:

```python runnable title="The value named once"
command = "attendance"

match command:
    case "report":
        print("Printing the report card")
    case "attendance":
        print("Showing the attendance sheet")
    case "marks":
        print("Showing the mark sheet")
    case _:
        print("Unknown command")
```

Same result, and the shape of the code now says what it is doing: one value, several possible cases. Each `case` line reads like the answer it is waiting for.

### Matching Variables

The value at the top of a `match` is called the **subject**. It can be a variable, or anything that produces a value.

```python runnable title="The subject can be worked out on the spot"
marks = 78

match marks // 10:
    case 10:
        print("Full marks")
    case 9:
        print("Ninety-something")
    case 7:
        print("Seventy-something")
    case _:
        print("Something else")
```

`78 // 10` is 7, so the third case matched.

### The match Statement

```text
match subject:
    case pattern:
        code to run
    case another pattern:
        code to run
```

The rules are the same shape as an `if`:

- the `match` line ends in a colon
- every `case` line ends in a colon
- `case` lines are indented inside the `match`
- the code for each case is indented again

```python runnable expect-error title="A match with nothing in it"
day = "Monday"

match day:
print("Weekday")
```

`IndentationError` — the `match` opened a block and nothing was inside it.

### The case Clause

A `case` holds a pattern and a block of code. Python tries the cases from the top and stops at the first match.

```python runnable title="Only the first match runs"
grade = "A"

match grade:
    case "A":
        print("Excellent")
    case "A":
        print("This line can never run")
    case _:
        print("Something else")
```

The second `case "A"` is unreachable. Python matched the first one and stopped, exactly as an `if elif` chain does.

### Basic Pattern Matching

The simplest pattern is a plain value to compare against.

```python runnable title="Matching a number"
lives = 2

match lives:
    case 3:
        print("Full lives")
    case 2:
        print("One lost")
    case 1:
        print("Last chance")
    case 0:
        print("Game over")
```

If several plain values should do the same thing, you could write each one as its own case. But there is a shorter way, which comes next.

### OR Pattern

A single `|` lets one case match several values. Read it as "or".

```python runnable title="One case, several values"
day = "Sunday"

match day:
    case "Saturday" | "Sunday":
        print("Weekend")
    case "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday":
        print("School day")
    case _:
        print("Not a day")
```

Two cases cover all seven days. The same thing as an `if` would need `day == "Saturday" or day == "Sunday"`, with the variable named twice.

```python runnable title="Grouping grades"
grade = "C"

match grade:
    case "A" | "B":
        print("Above average")
    case "C" | "D":
        print("Average")
    case "F":
        print("Needs help")
    case _:
        print("Unknown grade")
```

In a `case`, `|` means "or". It is not the bitwise OR operator here, even though it is the same symbol.

### Default Case (case _)

`case _` matches anything. The `_` is a single underscore, and it is Python's way of saying "I do not care what the value is".

It is the `else` of a `match`, and like `else` it goes last.

```python runnable title="Catching everything else"
command = "homework"

match command:
    case "report":
        print("Printing the report card")
    case "attendance":
        print("Showing the attendance sheet")
    case _:
        print("Unknown command:", command)
```

Without a `case _`, a value that matches nothing simply does nothing:

```python runnable title="No default, no output"
command = "homework"

match command:
    case "report":
        print("Printing the report card")
    case "attendance":
        print("Showing the attendance sheet")

print("Finished")
```

Nothing was printed about the command. No error, no warning. That silence is why a `case _` is worth adding even when you believe every value is covered.

Putting `case _` anywhere but last would make every case below it unreachable, and Python refuses outright:

```python runnable expect-error title="A default in the wrong place"
command = "report"

match command:
    case _:
        print("Unknown command")
    case "report":
        print("Printing the report card")
```

`SyntaxError: wildcard makes remaining patterns unreachable`. This is one of the few ordering mistakes a language will catch for you — the same mistake in an `if elif else` chain runs happily and gives the wrong answer.

### Matching Literal Values

A **literal** is a value written out in full: `78`, `"report"`, `True`, `None`.

```python runnable title="Different kinds of literal"
answer = None

match answer:
    case True:
        print("Yes")
    case False:
        print("No")
    case None:
        print("Not answered yet")
    case _:
        print("Something unexpected")
```

One trap is worth knowing early. A bare name in a `case` is **not** compared against. It captures the value instead, like a new box that catches whatever the subject was.

Python refuses to compile that when other cases follow it:

```python runnable expect-error title="A bare name with cases after it"
PASS_MARK = 35
marks = 78

match marks:
    case PASS_MARK:
        print("Matched the pass mark")
    case _:
        print("Did not match")
```

`SyntaxError: name capture 'PASS_MARK' makes remaining patterns unreachable` — Python saw that the case would swallow every value, leaving the cases below it dead, and refused the file.

That is the helpful version. The dangerous one is a bare name in the **last** case, where nothing is left to become unreachable:

```python runnable title="A bare name as the last case, silently matching"
PASS_MARK = 35
marks = 78

match marks:
    case 100:
        print("Full marks")
    case PASS_MARK:
        print("Matched the pass mark:", PASS_MARK)
```

It printed `Matched the pass mark: 78`. The file compiled, nothing warned, and `PASS_MARK` is now 78 inside that block — the case caught the subject and rebound the name over it.

To compare against a value, write the value out:

```python runnable title="Comparing against a value properly"
marks = 78

match marks:
    case 35:
        print("Exactly the pass mark")
    case _:
        print("Not the pass mark")
```

A name reached through a dot **is** compared, because it cannot be a capture:

```python runnable title="A dotted name is compared, not captured"
import math

value = 3.141592653589793

match value:
    case math.pi:
        print("That is pi")
    case _:
        print("Not pi")
```

So a constant works in a `case` when it lives on something — `math.pi`, or your own settings object — and not when it is a plain name.

A bare name in a `case` captures the value rather than comparing with it. With cases after it, Python refuses to compile the file. As the **last** case it compiles, matches everything, and quietly rebinds the name. Write the literal value instead.

### Matching Multiple Patterns

Patterns can also describe the **shape** of a value, not just its exact contents. A pair of values can be matched as a pair.

```python runnable title="Matching a pair of values"
result = ("maths", 78)

match result:
    case ("maths", marks):
        print("Maths marks:", marks)
    case ("science", marks):
        print("Science marks:", marks)
    case _:
        print("Unknown subject")
```

The first case matched because the subject is a pair whose first item is `"maths"`. The second item was captured into `marks`, which the block then used.

This is where a bare name earns its keep: inside a larger pattern, it captures the part you want. A bare name is dangerous when it fills a whole `case` by itself, but useful inside a pair like `("maths", marks)`, where it catches only one piece.

```python runnable title="Matching on how many items there are"
for result in [("maths", 78), ("science", 85, "A"), ()]:
    match result:
        case ():
            print("No result recorded")
        case (subject, marks):
            print(subject, "scored", marks)
        case (subject, marks, grade):
            print(subject, "scored", marks, "grade", grade)
```

Three different shapes, three different cases, and no counting or index checks anywhere.

A case can also carry an extra test, written with `if`. It is called a **guard**:

```python runnable title="A case with an extra test"
result = ("maths", 20)

match result:
    case (subject, marks) if marks >= 35:
        print(subject, "passed with", marks)
    case (subject, marks):
        print(subject, "failed with", marks)
```

The first case has a guard: it only runs when the shape matches **and** the guard is true. Here the shape matched, but `marks >= 35` was false, so Python moved on to the second case.

### Pattern Matching vs if-elif-else

Both do the same job. The difference is which one makes the code clearer.

<div className="tscroll">

| Use `match` when | Use `if elif else` when |
|---|---|
| One value is compared against many fixed options | Each branch tests something different |
| The options are literal values such as commands or codes | The tests are ranges, like `marks >= 80` |
| You are pulling pieces out of a pair or a list | There is only one condition |
| Several values share one outcome, using `\|` | A condition combines several variables |

</div>

Ranges are the clearest dividing line. A grade boundary is not a fixed value, so `if elif else` stays the right tool:

```python runnable title="Ranges belong in an if chain"
marks = 78

if marks >= 80:
    print("Grade A")
elif marks >= 60:
    print("Grade B")
elif marks >= 35:
    print("Grade C")
else:
    print("Fail")
```

Fixed options belong in a `match`:

```python runnable title="Fixed options belong in a match"
grade = "B"

match grade:
    case "A" | "B":
        print("Above average")
    case "C" | "D":
        print("Average")
    case _:
        print("Needs help")
```

Ask what the branches are testing. Many different questions means `if elif else`. One value against many fixed answers means `match`.

## Common mistakes

Each row is one mistake. The middle column is what you will actually see on screen.

<div className="tscroll">

| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| Using a bare name as a value to compare | `SyntaxError: name capture ... makes remaining patterns unreachable` | A bare name captures the value instead of comparing | Write the literal: `case 35:` |
| A bare name in the **last** case | It matches everything, with no error at all | Nothing follows it, so nothing becomes unreachable | Write the literal, or use `case _` |
| Putting `case _` first | `SyntaxError: wildcard makes remaining patterns unreachable` | `case _` matches anything, so nothing below it could be reached | Put `case _` last |
| Leaving out `case _` | Nothing happens, and no error | A value that matches nothing simply falls through | Add `case _` to catch the rest |
| Forgetting a colon on `match` or `case` | `SyntaxError: expected ':'` | Both lines open a block | End both lines with `:` |
| Not indenting the `case` lines | `IndentationError: expected an indented block` | `case` lines live inside the `match` block | Indent each `case` by four spaces |
| Using `\|\|` for "or" in a case | `SyntaxError: invalid syntax` | Python uses a single `\|` in a pattern | `case "A" \| "B":` |
| Expecting several cases to run | Only one message appears | `match` stops at the first case that fits | Combine the values with `\|` |
| Using `match` for ranges | Cases that never match | A pattern compares shapes and values, not ranges | Use `if elif else`, or add a guard with `if` |
| Repeating a case | A branch that silently never runs | An earlier identical case already matched | Remove the duplicate |

</div>

## What to Remember

- **`match` compares one value against several patterns and runs the first one that fits.**
- The value at the top is the subject. It is named once, not on every branch.
- The `match` line and every `case` line end in a colon, and each block is indented.
- Only one case ever runs, exactly as in an `if elif else` chain.
- A single `|` inside a pattern means "or", so one case can cover several values.
- `case _` matches anything. It is the `else` of a `match`, and it goes last.
- **Without `case _`, an unmatched value does nothing at all** — no output and no error.
- **A bare name in a `case` captures the value instead of comparing with it.** With cases after it Python refuses the file; as the last case it silently matches everything. Write the literal, or reach the constant through a dot.
- A pattern can describe a shape, pulling pieces out of a pair or a list as it matches.
- A `case` can carry an extra `if` test, called a guard.
- Use `match` for one value against fixed options, and `if elif else` for ranges and unrelated tests.
- `match` needs Python 3.10 or newer.

Next: **Loops**
