Why this matters
A match statement picks one block of code from several options, based on one value. Each option starts with a case line, such as case 2:. Python compares the value after match with each case in turn. The first case that fits runs, and the statement ends.
Many languages, such as C and Java, work differently. In those languages a matching case keeps running into the cases below it until a break stops it. Running from one case into the next is called fall-through. A reader who knows those languages will look for it here. Python's match has no fall-through, so a missing line never makes a case run into the next one.
This lesson shows what Python does in its place. The sections cover sharing one block between several values, writing shared work out in full, and the mistakes that still happen. The programs were run with Python 3.14.4. The match statement needs Python 3.10 or later.
The idea: one case runs, then the match ends
Start with a plain match. Read it once without running it and try to predict the output.
Output:
standard
doneThe value level is 2, so Python runs the block under case 2:. It prints standard. The block then ends, and the match ends with it. The other blocks are skipped. The line that prints done is after the match, so it always runs.
Nothing needs to stop the block. A block that finishes is the end of the match, so there is no break line to forget.
A break inside a loop ends the loop
A break is allowed in a match only when the match sits inside a loop. In that case the break ends the loop, not just the match. The next program has a match inside a for loop.
Output:
value 0
after the match, i = 0
found one, leaving the matchWhen i is 1, the break ends the for loop. The loop never reaches i = 2, and the print after the match does not run for i = 1. To leave only the match and keep the loop going, put the rest of the work in an if block instead of using break.
A break outside any loop is a mistake. The next program has one.
match 2:
case 2:
print("two")
breakPython refuses the file before it runs:
File "main.py", line 4
break
^^^^^
SyntaxError: 'break' outside loopThe path in the first line depends on where the file is saved.
Case stacking with |
In some languages, several labels can sit one above another with no code between them. They then share the code under them. In Python, the same result comes from a pattern that joins the values with |, which means "or".
Output:
day 1 is a weekend
day 2 is a weekday
day 3 is a weekday
day 4 is a weekday
day 5 is a weekday
day 6 is a weekday
day 7 is a weekendThe pattern 1 | 7 fits when the value is 1 or 7. Both values run the same block. Because the pattern is a single pattern, no value falls into a block below it, so there is nothing for a reader to check.
Planned sharing: write the shared work out
Some programs want a higher level to get everything that the lower levels get. In a language with fall-through, the code for the top level runs into the code below it. Python has nothing that runs into another case, so the shared work has to be written in a way that makes the sharing clear.
The first way is to keep the work in a list and take the first few items. A slice is a part of a list. PERKS[:level] means the first level items of PERKS.
Output:
level 1 gets 1 perks: ['email support']
level 2 gets 2 perks: ['email support', 'extra storage']
level 3 gets 3 perks: ['email support', 'extra storage', 'priority support']
level 4 gets 0 perks: []The check level in (1, 2, 3) looks at a tuple, which is a fixed list written in parentheses. Level 4 is not in the tuple, so it gets no perks. Each higher level gets the lower levels' perks because the slice starts at the first item every time.
Worked example: plan tiers
Here is a function for three plans. Each higher plan includes the features of the plans below it. The plans basic and trial share one line of code.
The first version writes each plan's full list in its own block.
Output:
pro: ['priority support', 'extra storage', 'email login']
plus: ['extra storage', 'email login']
trial: ['email login']
Traceback (most recent call last):
...
print("gold:", features_for("gold"))
...
raise ValueError(f"unknown tier: {tier}")
ValueError: unknown tier: goldThe lines between the first line and the last line are shortened with ..., because the file paths depend on the machine. raise stops the function and reports the error.
Each block returns its full list. The pro block repeats the lines that plus and basic also have. That repetition is the cost of having no fall-through, and it is easy to get wrong when a line is changed in one block and not the others.
The version below builds each list from the list below it. The + operator joins two lists into one new list.
Output:
pro: ['priority support', 'extra storage', 'email login']
plus: ['extra storage', 'email login']
trial: ['email login']The function returns list(PRO) rather than PRO itself. list(...) makes a copy. Without the copy, a caller who added an item to the returned list would change PRO for every later call.
Common mistakes
A missing break does not change the result
A match has no break to forget. The next program has no break after one. In Python, it prints one only.
Output:
oneWhen you port a switch that depended on fall-through, the output changes, because the lower cases no longer run. Find the cases that the code meant to share. Then write them with |, or write out their work in each block, as in the plan tiers example.
A name set in one case is not set in another
Python does not give each block its own scope. A name that is set in one block belongs to the whole function. Reading that name in a block that did not set it causes an error when that block runs. The next program sets y in one case and reads it in another.
Output:
5
Traceback (most recent call last):
...
run(2)
...
print(y)
^
UnboundLocalError: cannot access local variable 'y' where it is not associated with a valueThe first call prints 5. The second call reads y before any line in that call has set it. Python reports this only when the line runs, so a test has to reach that line to find the problem. The fix is to set the name before the match, or to put all the work for a case in that case's block.
A repeated pattern is not an error
Python does not check for repeated patterns. In the next program, the second case 1 never runs, and no message appears.
Output:
oneTo fix it, remove the second pattern, or join the two with | in one case.
A wildcard above other cases stops the program
The _ pattern fits any value, so it must be the last case. Python refuses a file where any case comes after it.
code = 2
match code:
case _:
print("other")
case 2:
print("two")Python refuses the file before it runs:
File "main.py", line 3
case _:
^
SyntaxError: wildcard makes remaining patterns unreachableReading a match you did not write
When a match is new to you, read it in three passes. Together they show where each value's work comes from.
The first pass finds each case block. Cases that share a block are joined with | on one line, and they need no further check.
The second pass checks that _ is the last case, if there is one. Python refuses the file otherwise, so this is a quick check.
The third pass reads each block to its end and asks what it does for every value in its pattern. A block never hands work to the block below it. If one value should get the work of another value, the code must call a function, read a shared list, or repeat the lines. If none of those is there, the author has to decide whether the missing work was intended.
Practice
Write each answer in a file, run it with python3 file.py, and compare the result with the answer.
- Predict the output of this program before you run it.
n = 4
match n:
case 4:
print("a", end="")
case 5:
print("b", end="")
case 6:
print("c", end="")
print("d")Show the answer
The output is ad on one line. The value 4 runs the block under case 4:, which prints a. The end="" part of print stops it from adding a newline. There is no run into case 5:, so b is not printed. The print("d") after the match prints d and a newline.
- Write a function
box_message(size)that returnssmall boxforsmall,medium boxformedium, andlarge boxforlargeorxl. Any other size returnsunknown size. Use one block forlargeandxl.
Show the answer
def box_message(size):
match size:
case "small":
return "small box"
case "medium":
return "medium box"
case "large" | "xl":
return "large box"
case _:
return "unknown size"box_message("small") returns small box only. The medium block never runs for small, and no break is needed.
- Write a function
days_in(month, leap_year)that returns the number of days in a month. Months 1, 3, 5, 7, 8, 10 and 12 have 31 days. Months 4, 6, 9 and 11 have 30 days. February has 29 days in a leap year and 28 days otherwise. Any other month raises aValueError. Use|for each group of months.
Show the answer
def days_in(month, leap_year):
match month:
case 1 | 3 | 5 | 7 | 8 | 10 | 12:
return 31
case 4 | 6 | 9 | 11:
return 30
case 2:
return 29 if leap_year else 28
case _:
raise ValueError(f"bad month: {month}")days_in(3, False) returns 31, days_in(9, False) returns 30, and days_in(2, True) returns 29. days_in(13, False) raises ValueError: bad month: 13.
- Write a function
is_vowel(c)that returnsTruefor a, e, i, o and u, in either upper or lower case. Use.lower()and|.
Show the answer
def is_vowel(c):
match c.lower():
case "a" | "e" | "i" | "o" | "u":
return True
case _:
return Falseis_vowel("E") returns True, and is_vowel("t") returns False. Lowering the letter first means the patterns only need lowercase letters.
- A teammate changes the
_block of amatchonLighttopass, so that the function never raises an error. Explain in plain words what this costs.
Show the answer
A member added to Light later falls into the _ block, does nothing, and the program keeps going with no sign of a problem. Keep an error in the _ block, for example by calling assert_never(light), so that a new member stops the program at once.
Summary
- A Python
matchruns the block of the first case that fits, then ends. Blocks never run into the block below them. - Joining values with
|in one case lets them share one block, with no fall-through to check. - The work that another language would get from fall-through must be written out, kept in a list, or put in a function.
- A
breakends the loop around amatch, and abreakoutside any loop is a syntax error. - A repeated case is not an error, a
_case above other cases is an error, and a name set in one case may be unset in another.