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"orNone. - Wildcard `_`
- A pattern that matches anything and stores nothing. Used as the "default" case.
- OR pattern
- Alternatives joined with
|, such as301 | 302. - Capture pattern
- A bare name that matches anything and stores the matched value in that name.
- Guard
- An
ifcondition 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.
matchandcasecan 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.
status = 404
match status:
case 200:
print("OK")
case 301 | 302:
print("Redirect")
case 404:
print("Not found")
case _:
print("Other status")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.
status = 500
match status:
case 200:
print("OK")
case 301 | 302:
print("Redirect")
print("after the match") # reached; nothing matched, no error03Capture 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.
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.
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.)
command = ["say", "good", "morning"]
match command:
case ["say", *words]:
print("say:", " ".join(words)) # say: good morning06Mapping 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.
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.
RED = 1
color = 5
match color:
case RED: # captures! does NOT compare with 1
print("matched RED?")
print("RED is now", RED) # 508match 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
Walk the cases
In the first example set
status = 302,200, and418. Say which line prints each time before you run. Then delete thecase _block and run with 418: nothing prints, and no error appears. - 2
Add a new shape
Add a case
["add", a, b]to the command match that printsa + b, and changecommandto["add", 2, 3]. You should see 5. - 3
Trigger the trap safely
Run the capture trap example with
color = 99. It still prints "matched RED?". Now replacecase RED:withcase 1:and run again withcolor = 99: nothing matches, andREDstays 1.
Code & diagrams
Change status to 301, then to 999, and predict each result. Change temperature to 20 and 5.
Expected output
Not found
hot 35Change 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.
Expected output
on the y axis at 5
move 3 steps north
click at 10 20
matched RED?
RED is now 5Break 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.
value = 3
match value:
case anything:
print("first")
case 3:
print("three")Break #2
Compare with a variable by name
Write a variable name in a case and expect it to be compared.
RED = 1
color = 5
match color:
case RED:
print("matched RED?")
print("RED is now", RED)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).
matchandcaseare soft keywords, so older code that usesmatchas a variable name (for example the result ofre.match) still works. - ▸
Literal
None,TrueandFalseare compared by identity (is); other literals and dotted names (value patterns such asColor.RED) by equality (==). Socase 1:matches the subjectTrue(because1 == True), butcase True:does not match the subject1. 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,bytesandbytearray(so lists, tuples andrangeall 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. Anasclause 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)withcase (1, y) if y > 5first, thencase (x, 2):yis 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
matchfits: for examplecase {"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
The form is
match value:followed by indentedcase pattern:blocks. The value aftermatchis called the subject. Python tries the cases from top to bottom and runs the block of the first one that matches, then leaves the wholematch. There is no fall-through (it never slides on into the next case) and nobreakto write. If nothing matches, nothing runs and there is no error. The wildcard_matches anything, socase _:at the end is the "default" branch. - 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 anifafter the pattern:case t if t > 30:matches only when the extra condition is also true, which lets you test ranges. - 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:bindstto the subject so the guard and the block can use it. Capture is what makesmatchmore thanif/elif: you can pull parts out of the value as you test its shape. - 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 intoy;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 listwords. Text (str) is deliberately not matched as a sequence of letters. - 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 whymatchis popular for parsing commands and API messages. - 6
The great trap: **a bare name in a
caseis a capture, not a comparison with a variable.** IfRED = 1and you writecase RED:, Python does not testcolor == RED. It matches everything and *overwrites*REDwith the subject. - 7
Python even rejects a capture that is followed by more cases (
SyntaxError). To compare with a named constant, use a dotted name such asColor.RED(enums, Topic 8.9) or write the literal. - 8
Also remember:
matchneeds Python 3.10 or newer. For simple "value to value" lookups, a dictionary orif/elifmay be clearer.
Explain it without notes
How is match different from a C-style switch?
What does a bare name in a case do, and what is the trap?
What does a mapping pattern do with extra keys?
When would you choose if/elif or a dictionary over match?
Practice
Set day = 6. Use match to print Weekend for 6 or 7, Weekday for 1 to 5, and Invalid otherwise.
Set point = (3, 0). Use match to print Origin, On the x axis, On the y axis or Elsewhere.
Set command = ["add", "milk", "eggs"]. Use a star pattern to print Adding milk and eggs.
Set user = {"name": "Asha", "role": "admin", "age": 30}. Use a mapping pattern to print Admin: Asha.
Trade-offs
- ↔
match vs if/elif.
matchis clearer for shapes and captures, but needs Python 3.10+ and has the capture trap.if/elifworks 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
matchwith 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/elifand a dictionary.