Command Palette

Search for a command to run...

Hectal
PHASE 2Beginner Python 3.10+ ~34 min· topic 8 of 9

Topic 2.8

Structural Pattern Matching: match

In one line

match compares a value against a series of patterns and runs the first case that fits. It can test exact values, alternatives, the shape of lists and dictionaries, and capture parts of what it matched into variables. It is not a C-style switch: a bare name in a case captures, it does not compare.

Think of it like this

A post-office sorter. A postcard goes in tray one. A parcel with two labels goes in tray two. A letter addressed to a company goes in tray three. The sorter does not ask "is this number equal to 7?". She looks at the shape of what is in her hand and picks the first tray whose description fits. match works like that: you describe shapes (patterns) and Python finds the first one that fits.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Pattern
A description of the shape or value you are looking for, written after case.
Subject
The value placed after match, which the patterns are tried against.
Literal pattern
A pattern that matches an exact value such as 404, "stop" or None.
Wildcard `_`
A pattern that matches anything and stores nothing. Used as the "default" case.
OR pattern
Alternatives joined with |, such as 301 | 302.
Capture pattern
A bare name that matches anything and stores the matched value in that name.
Guard
An if condition after a pattern that must also be true for the case to match.
Sequence / mapping pattern
A pattern shaped like a list or tuple / like a dictionary.
Soft keyword
A word that is special only in certain places. match and case can still be used as variable names elsewhere.

Step by step

01Literals and the wildcard

Python compares status with each literal in order. 404 matches the third case, so it prints "Not found" and leaves the match. The _ case would run only if no earlier case matched.

main.pywhole filepython
status = 404
match status:
    case 200:
        print("OK")
    case 301 | 302:
        print("Redirect")
    case 404:
        print("Not found")
    case _:
        print("Other status")
Literals and the wildcarddiagram
Rendering diagram…

02OR patterns, and no match at all

301 | 302 is one case that accepts either number. If the subject fits no case and there is no _, the whole match does nothing and the program carries on. That is quiet, so add a case _: when an unknown value should be reported.

main.pywhole filepython
status = 500
match status:
    case 200:
        print("OK")
    case 301 | 302:
        print("Redirect")
print("after the match")      # reached; nothing matched, no error

03Capture and guard

t matches any value and holds it, and the guard if t > 30 adds a test. If the guard is false, Python goes on to the next case. This is how you match ranges, which literals alone cannot.

main.pywhole filepython
temperature = 35
match temperature:
    case t if t > 30:
        print("hot", t)
    case t if t > 15:
        print("warm", t)
    case _:
        print("cold")

04Sequence patterns: tuples and lists

(0, 5) is a tuple: a fixed pair of values. It fails (0, 0) (second item differs), then matches (0, y) and puts 5 into y. In the second match, "move" is a literal and direction and steps are captures. A pattern must have the same number of items as the subject, unless it has a *name that soaks up the rest.

main.pywhole filepython
point = (0, 5)
match point:
    case (0, 0):
        print("origin")
    case (0, y):
        print("on the y axis at", y)
    case (x, y):
        print("somewhere at", x, y)

command = ["move", "north", 3]
match command:
    case ["quit"]:
        print("bye")
    case ["move", direction, steps]:
        print("move", steps, "steps", direction)

05The star: any number of items

*words collects all the remaining items into a list. It can match zero items too, so ["say"] also matches this case with words empty. (" ".join(words) glues the words together with a space between them; Phase 3 teaches join.)

main.pywhole filepython
command = ["say", "good", "morning"]
match command:
    case ["say", *words]:
        print("say:", " ".join(words))      # say: good morning

06Mapping patterns: dictionaries

A dictionary maps keys to values (Phase 4). The pattern needs the listed keys with those values or captures, and ignores any other keys. Here "button" is in the data but not in the pattern, and that is fine.

main.pywhole filepython
event = {"type": "click", "x": 10, "y": 20, "button": "left"}
match event:
    case {"type": "click", "x": x, "y": y}:
        print("click at", x, y)
    case {"type": "key", "key": k}:
        print("key", k)

07The capture trap

RED here is a capture pattern, not the constant. It matches the number 5 and rebinds RED to 5. It would have matched any value at all. The earlier value is lost without any warning. This is the biggest difference from a C switch.

main.pywhole filepython
RED = 1
color = 5
match color:
    case RED:                 # captures! does NOT compare with 1
        print("matched RED?")
print("RED is now", RED)      # 5

08match vs if/elif, and when not to use it

For two or three plain value tests, if/elif is just as good. For "turn a value into another value", a dictionary is shorter. match earns its place when the shape of the data matters: commands as lists, messages as dictionaries, nested records. Code that has to run on Python older than 3.10 cannot use it at all.

Try it yourself

  1. 1

    Walk the cases

    In the first example set status = 302, 200, and 418. Say which line prints each time before you run. Then delete the case _ block and run with 418: nothing prints, and no error appears.

  2. 2

    Add a new shape

    Add a case ["add", a, b] to the command match that prints a + b, and change command to ["add", 2, 3]. You should see 5.

  3. 3

    Trigger the trap safely

    Run the capture trap example with color = 99. It still prints "matched RED?". Now replace case RED: with case 1: and run again with color = 99: nothing matches, and RED stays 1.

Code & diagrams

Literals, OR patterns, guards and the wildcard Python 3.10+ New tab

Change status to 301, then to 999, and predict each result. Change temperature to 20 and 5.

Sign in to run this example in your browser.

Expected output

Not found
hot 35
Sequences, mappings and the capture trap Python 3.10+ New tab

Change point to (7, 0) and command to ["say", "hi", "there"]. Last, read the two final lines: the match on RED succeeded for the wrong reason.

Sign in to run this example in your browser.

Expected output

on the y axis at 5
move 3 steps north
click at 10 20
matched RED?
RED is now 5
How match picks a casediagram
Rendering diagram…

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

A capture before other cases

Put a bare name as the first case, before a literal.

main.pywhole filepython
value = 3
match value:
    case anything:
        print("first")
    case 3:
        print("three")
terminal
$ python main.py
── what you'll see ──
File "main.py", line 3
SyntaxError: name capture 'anything' makes remaining patterns unreachable

Break #2

Compare with a variable by name

Write a variable name in a case and expect it to be compared.

main.pywhole filepython
RED = 1
color = 5
match color:
    case RED:
        print("matched RED?")
print("RED is now", RED)
terminal
$ python main.py
── what you'll see ──
matched RED?
RED is now 5

Myth vs fact

Myth

match is Python's switch statement.

Fact

It is more general: it matches shapes and can capture parts. It also has no fall-through, and a bare name captures rather than compares.

Myth

case RED: compares with my variable RED.

Fact

It captures the subject into RED. Only literals and dotted names are compared by value.

Myth

If no case matches, Python raises an error.

Fact

Nothing runs and no error is raised. Add case _: when an unknown value should be reported.

Myth

match is always better than if/elif.

Fact

For plain value tests if/elif is just as clear. match wins when the shape of the data matters.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Structural pattern matching was added in Python 3.10 (PEPs 634, 635 and 636). match and case are soft keywords, so older code that uses match as a variable name (for example the result of re.match) still works.

  • ▸

    Literal None, True and False are compared by identity (is); other literals and dotted names (value patterns such as Color.RED) by equality (==). So case 1: matches the subject True (because 1 == True), but case True: does not match the subject 1. Be careful when the subject could be a bool or an int.

  • ▸

    The matching rules interviewers ask about: a sequence pattern matches any sequence except str, bytes and bytearray (so lists, tuples and range all fit) and needs the exact length unless it has one *name. A mapping pattern matches any mapping, needs only its listed keys (keys must be literals or dotted names) and can collect the rest with **rest. An as clause names a whole sub-pattern: case [int() as a, int() as b]:. The subject is evaluated once, and OR alternatives must all bind the same names.

  • ▸

    Captures are bound as the pattern is tried, and a guard runs after binding. If a guard fails, the names it bound stay set (try match (1, 2) with case (1, y) if y > 5 first, then case (x, 2): y is still 2 afterwards). Do not rely on names from a case that did not run.

  • ▸

    Patterns can also match instances of classes, using case Point(x=0, y=y):. With dataclasses (Topic 8.8) this is a clean way to branch on object types and fields; Topic 10.8 covers class patterns, nested patterns and exhaustiveness checking.

  • ▸

    In AI and API code, messages are dictionaries and lists, so match fits: for example case {"role": "assistant", "content": text}: versus a message carrying a tool call. Validate the data with Pydantic (Topic 15.4) first when it comes from outside.

Remember this

  1. 1

    The form is match value: followed by indented case pattern: blocks. The value after match is called the subject. Python tries the cases from top to bottom and runs the block of the first one that matches, then leaves the whole match. There is no fall-through (it never slides on into the next case) and no break to write. If nothing matches, nothing runs and there is no error. The wildcard _ matches anything, so case _: at the end is the "default" branch.

  2. 2

    Literal patterns compare against an exact value: numbers, strings, True, False, None. case 404: is true when the subject equals 404. OR patterns join alternatives with |: case 301 | 302: matches either. A guard adds an if after the pattern: case t if t > 30: matches only when the extra condition is also true, which lets you test ranges.

  3. 3

    A capture pattern is a bare name (a name written alone, with nothing else around it). It matches anything and stores (binds) the value in that name. case t if t > 30: binds t to the subject so the guard and the block can use it. Capture is what makes match more than if/elif: you can pull parts out of the value as you test its shape.

  4. 4

    Sequence patterns look like lists or tuples (a tuple is a fixed group of values such as (0, 5), Topic 4.3) and match any list or tuple with that shape: case (0, y): matches a pair whose first item is 0 and puts the second into y; case ["move", direction, steps]: matches a three-item list starting with "move"; case ["say", *words]: matches "say" followed by any number of items, which are gathered into a list words. Text (str) is deliberately not matched as a sequence of letters.

  5. 5

    Mapping patterns look like dictionaries (a dictionary pairs keys with values, such as {"x": 10}, Topic 4.4): case {"type": "click", "x": x, "y": y}: matches any dictionary that has those keys with "type" equal to "click", and captures the other two values. Extra keys are ignored, which is what you want for messages and JSON data (a common text format for data) that may carry more fields. This is why match is popular for parsing commands and API messages.

  6. 6

    The great trap: **a bare name in a case is a capture, not a comparison with a variable.** If RED = 1 and you write case RED:, Python does not test color == RED. It matches everything and *overwrites* RED with the subject.

  7. 7

    Python even rejects a capture that is followed by more cases (SyntaxError). To compare with a named constant, use a dotted name such as Color.RED (enums, Topic 8.9) or write the literal.

  8. 8

    Also remember: match needs Python 3.10 or newer. For simple "value to value" lookups, a dictionary or if/elif may be clearer.

Explain it without notes

01

How is match different from a C-style switch?

02

What does a bare name in a case do, and what is the trap?

03

What does a mapping pattern do with extra keys?

04

When would you choose if/elif or a dictionary over match?

Practice

01

Set day = 6. Use match to print Weekend for 6 or 7, Weekday for 1 to 5, and Invalid otherwise.

02

Set point = (3, 0). Use match to print Origin, On the x axis, On the y axis or Elsewhere.

03

Set command = ["add", "milk", "eggs"]. Use a star pattern to print Adding milk and eggs.

04

Set user = {"name": "Asha", "role": "admin", "age": 30}. Use a mapping pattern to print Admin: Asha.

Trade-offs

  • ↔

    match vs if/elif. match is clearer for shapes and captures, but needs Python 3.10+ and has the capture trap. if/elif works everywhere.

  • ↔

    match vs a dictionary. A dictionary is shortest for value-to-value lookups, but it cannot look inside a structure or run code per case.

Done when you can

  • Done when you can write a match with literal, OR, wildcard and guard cases.

  • Done when you can match a tuple, a list with *rest, and a dictionary, and capture parts.

  • Done when you can explain why case RED: does not compare and how to fix it.

  • Done when you can say what happens when no case matches.

  • Done when you can choose between match, if/elif and a dictionary.