Why it matters
Many decisions compare one value against a list of fixed options. A menu has five choices, a day number maps to one of seven names, and a traffic light has three colours. You can write each of these as an if and elif ladder, but a long ladder repeats the same variable name on every line, and it is easy to lose track of which option you are on. The match statement says the same thing more directly. It looks at one value, then runs the block of the first option that fits.
Python added match in version 3.10. Older programs use the if ladder for the same job, and you will read both. This lesson shows how match works, the traps that come with it, and how it handles strings, None, enums and ranges. It also covers the one difference from many other languages that catches experienced programmers: a match never falls through to the next case, so there is no break to remember.
The idea: one value, many labelled doors
Think of a lift panel with buttons labelled 1 to 10. You press one button, and the lift goes to exactly that floor. The lift does not check every floor in turn. It reads the button you pressed and goes to the matching door. A match works the same way. It reads one value, finds the case that fits it, and runs that case's block.
Here is a complete program. It reads a day number from the user and prints the name of the day. Type 3 and press Enter.
day = int(input())
match day:
case 1:
print("Monday")
case 2:
print("Tuesday")
case 3:
print("Wednesday")
case 4:
print("Thursday")
case 5:
print("Friday")
case 6:
print("Saturday")
case 7:
print("Sunday")
case _:
print("Not a day number")The output for 3 is:
WednesdayRead the program in parts. match day: is the header. It names the value to examine, which is day here. The lines under it are indented, and each case 3: is a label with a colon at the end. The block under a case runs when the value fits that case. The last case, case _:, is the default. The underscore means "anything". It is optional, but leaving it out means that an unknown value does nothing at all, as you will see later.
Trace the run. day holds 3. Python tries case 1, which does not fit. It tries case 2, which does not fit. It tries case 3, which fits. It prints Wednesday, and the match ends. The other cases are never tried after a match. Type 9 and the case _: block prints Not a day number.
What a match can test
The value after match can be any type of Python value. That is wider than many languages allow. A case can be:
- a fixed number or string, such as
case 3:orcase "stop": None, writtencase None:- a named enum member, such as
case Light.RED: - a type with a name for the value, such as
case int(n): - a sequence shape, such as
case (x, y): - any pattern with a condition added, such as
case n if n > 10:, which is called a guard - the default,
case _:
Each of these is a pattern. A pattern describes a shape that a value can have. The match checks each pattern from the top and runs the first one that fits.
A case can list several fixed values with |, which reads as "or". You will see that in the next section.
Patterns cannot describe a range such as "from 60 to 69" by themselves. A pattern with a plain name, such as case x:, matches anything and gives that anything the name x. For ranges, use a guard, which is covered later in this lesson.
No fall-through
In many languages, a switch statement keeps running the code under the next label when a case has no break. That behaviour is called fall-through. Python's match does not do this. When a case's block ends, the whole match ends. A break is not needed, and Python does not have one for match.
Here is a program that would show fall-through in some other languages. The value is 2, so the match is case 2.
n = 2
match n:
case 1:
print("one")
case 2:
print("two")
case 3:
print("three")twoOnly two prints. The block for case 3 does not run, even though it comes right after the matching block. Do not expect the cases below the matching one to run. Find the case that matched, run its block, and stop.
When several values should run the same block, list them with |:
for ch in "eK":
match ch.lower():
case "a" | "e" | "i" | "o" | "u":
print("vowel")
case _:
print("consonant")vowel
consonantThe ch.lower() call changes the letter to lower case before the match, so E and e both count as vowels. The | line means "any one of these values". Use it whenever the same block should run for several values.
Python does not accept or in a case. Writing case 1 or 2: stops the program with a syntax error. The | symbol is the only way to list alternatives in a pattern.
Strings and None
A match can compare strings. Two strings with the same letters match, because the comparison is about the text, not about the object in memory. The next program checks a command.
cmd = "stop"
match cmd:
case "go":
print("Moving")
case "stop":
print("Halting")
case _:
print("Unknown")HaltingNow the case that catches many beginners. None is Python's word for "no value". In some languages, a switch on a missing value crashes the program. Python's match does not crash. It checks the cases in order, and None fits only the cases that name it or the default.
cmd = None
match cmd:
case "go":
print("Moving")
case "stop":
print("Halting")
case _:
print("Unknown")UnknownThe program keeps running, but the answer may be wrong for your purpose. If a missing command should get its own message, add a case for it, as in case None:. Put that case above the default.
A calculator with match
A calculator reads a number, an operator and another number. The operator is a single character, such as + or *. Each operator has its own case. Type 12, then *, then 4, each on its own line.
x = int(input())
op = input()
y = int(input())
match op:
case "+":
print(x + y)
case "-":
print(x - y)
case "*":
print(x * y)
case "/":
if y == 0:
print("Cannot divide by zero")
else:
print(x / y)
case _:
print("Unknown operator")With 12, * and 4, the output is 48. Type 7, / and 0 to see Cannot divide by zero. Type 7, ^ and 2 to see Unknown operator.
Each case is a block, so the / case can hold an if of its own. The guard if y == 0: runs before the division, so the program never divides by zero. The division x / y always gives a float in Python. Type 7, / and 2 and you get 3.5. Use x // y when you want the whole-number part, which is 3 here.
Notice what is missing. The program does not check that the first and last lines are whole numbers. If you type twelve, int(input()) stops with a ValueError. Real programs check their input before using it, and later lessons in this module show how.
Enums: named values
An enum is a type that lists a fixed set of names. Each name is a constant. Python has an enum module for this. The example below defines three lights and matches on them.
from enum import Enum
class Light(Enum):
RED = 1
YELLOW = 2
GREEN = 3
now = Light.YELLOW
match now:
case Light.RED:
action = "Stop"
case Light.YELLOW:
print("Light is changing")
action = "Slow down"
case Light.GREEN:
action = "Go"
print(action)Light is changing
Slow downThe case labels use the full name, Light.RED, not just RED. This is required. A bare name in a case is a capture. It matches any value and gives that value a new name. Writing case RED: does not compare with the enum member. It declares a new variable called RED and matches everything. Python stops you when the capture would hide the cases below it:
File "main.py", line 10
case RED:
^^^
SyntaxError: name capture 'RED' makes remaining patterns unreachableThe message names the problem: the capture RED would catch every value, so the cases after it could never run. Write the enum name with a dot, as case Light.RED:, and the match compares with the member.
An enum member prints with its class name. print(Light.RED) shows Light.RED. The name attribute gives the text RED, and the value attribute gives the number 1 that was written in the class.
Getting a value back from a match
A match is a statement, not an expression. Python has no match that gives back a value in one line, unlike some languages that have a switch expression. There are three common ways to get a value out.
The first is to assign the value inside each case. The program above does this with action. If no case assigns the variable, the print at the end fails with a NameError, so every case should assign it or a default should.
The second is to return from inside each case, when the match is inside a function. The function below gives back the text for each choice. A return leaves the function at once, so no break is needed.
def message(choice):
match choice:
case 1:
return "Starting the game"
case 2:
return "Help"
case 3:
return "Goodbye"
case _:
return "Please choose 1, 2 or 3"
print(message(2))
print(message(9))Help
Please choose 1, 2 or 3This form separates the decision from the printing. The function only decides the text, and the caller decides what to do with it. That is useful for testing, because you can call message with many values and check what it returns without reading the screen.
The third is a dictionary. For a plain lookup from fixed keys to values, a dictionary is shorter than a match:
days = {1: "Monday", 2: "Tuesday", 3: "Wednesday"}
print(days.get(3, "Not a day number"))
print(days.get(9, "Not a day number"))Wednesday
Not a day numberThe get method returns the value for a key, or the second argument when the key is missing. Use match when each case runs its own code. Use the dictionary when each case only gives back a value.
Ranges and guards
A pattern cannot say "from 60 to 69". A guard adds a condition to a case with the word if. The case matches only when the pattern fits and the guard is True. Give the value a name in the case, then test it in the guard.
for n in (95, 75, 59, 60):
match n:
case x if x >= 90:
print("A")
case x if x >= 75:
print("B")
case x if x >= 60:
print("C")
case _:
print("Fail")A
B
Fail
CThe cases are checked from the top, so the order matters, exactly as it does in an elif ladder. The value 60 reaches the third case, because it fails the first two guards. The value 59 fails all three guards and reaches the default.
A guard turns a match into a ladder. When every test compares one value against a plain range, an if ladder is clearer. Use a guard when the same value also has a shape to check, as in the next section.
Matching the shape of a value
A class pattern checks the type of a value and gives its parts a name. The example below handles an int, a string and anything else. The int case uses a guard, and the value 42 fits the first case.
def describe(value):
match value:
case int(n) if n > 10:
return "big int " + str(n)
case int(n):
return "small int " + str(n)
case str(s):
return "text " + s
case _:
return "something else"
print(describe(42))
print(describe(3))
print(describe("hi"))
print(describe(2.5))big int 42
small int 3
text hi
something elseA sequence pattern checks the shape of a tuple or a list. The name in each position is set to the value at that position. This is the usual form for a point on a grid:
point = (3, 0)
match point:
case (0, 0):
print("origin")
case (x, 0):
print("on x axis at", x)
case (x, y):
print("point", x, y)on x axis at 3The three cases are checked from the top. The point (3, 0) does not fit (0, 0), but it fits (x, 0) with x set to 3. The last case would fit any pair.
These forms are the part of match that goes beyond the classic switch. You will not need them in every program, but they make it short to handle data of different shapes in one place.
A menu from start to finish
A menu is the most common real use of match. The program shows a list of options, reads the number the user types, and runs the action for that number. The part that makes a menu good is the default case, which tells the user what they are allowed to type.
choice = int(input())
match choice:
case 1:
print("Starting the game")
case 2:
print("Help: move with the arrow keys")
case 3:
print("Goodbye")
case _:
print("Please choose 1, 2 or 3")With 2, the output is Help: move with the arrow keys. With 0, the output is Please choose 1, 2 or 3.
Trace the input 2. The value is 2. Python tries case 1, which does not fit. It tries case 2, which fits. It prints the help line, and the match ends. The case 3 block is never tried, and neither is the default. Each option has its own block, so the program never prints two messages for one choice.
A menu that silently does nothing for an unknown number looks broken. The default case gives the user a clear next step, which is the difference between a program that works and one that feels finished.
Testing a match with a table
A match is easy to test, because each case is a separate block. Write a table with one row for each case, one row for a value below the smallest case, one row for a value above the largest, and one row for any gap between the cases. Then run the program with each input and compare.
| Input | Expected output |
|---|---|
| 1 | Starting the game |
| 2 | Help: move with the arrow keys |
| 3 | Goodbye |
| 0 | Please choose 1, 2 or 3 |
| 4 | Please choose 1, 2 or 3 |
| 99 | Please choose 1, 2 or 3 |
The table catches a bug that the program would otherwise hide. If one case printed the wrong text, its row would show it at once. A match with three cases needs at least five rows, and a match with many cases needs a row for each case, because a copy-paste error can hide in any one of them.
Test the default with two values, one just outside the range and one far outside it. A default that handles 4 but fails on 99 is a bug you will only find in production if you do not test it.
An enum as a state machine
A state machine is a program that is always in one of a few named states and moves to another state when something happens. A traffic light is the classic example. It is always red, green or yellow, and it moves in a fixed order. An enum holds the named states, and a function uses a match to decide the next one.
from enum import Enum
class Light(Enum):
RED = 1
GREEN = 2
YELLOW = 3
def next_light(current):
match current:
case Light.RED:
return Light.GREEN
case Light.GREEN:
return Light.YELLOW
case Light.YELLOW:
return Light.RED
now = Light.RED
for _ in range(4):
print(now.name)
now = next_light(now)RED
GREEN
YELLOW
REDThe next_light function has one case for every state. Python does not check that every state has a case. If a team later adds a fourth colour to the enum and forgets this function, the function returns None for that colour, and the program carries on with the wrong state. Test this with a table of every state, and use a type checker such as mypy if your project has one. The standard library's typing.assert_never function can make the missing case visible. Put it in a last case that catches the leftover value, and a type checker can then report the missing state.
Notice how the code reads. Light.RED is a name you can check against the traffic rules. The function says what follows each state in one place, which is the only place the rule lives. This pattern appears in vending machines, network protocols, game screens and form wizards.
Choosing between match and an if ladder
Both tools choose one branch from several. A match compares one value against fixed patterns. An if ladder tests any condition, including conditions on different variables. A simple rule helps.
| Situation | Better choice | Why |
|---|---|---|
| One value, a short list of fixed values | match | Each option is labelled, and the value is read once |
| A range such as 0 to 59 | if ladder, or a match with guards | A pattern alone cannot describe a range |
| Tests on two or more variables | if ladder | A match tests one value only |
| A value that is an enum | match | The enum members give names to each case |
The phrase "one value" is the key idea. If you find yourself writing cases that test different variables, use an if ladder.
To see the difference, here is the same day-name choice written both ways. The first version uses an if ladder, and the second uses a match with the same options.
day = 2
if day == 1:
print("Monday")
elif day == 2:
print("Tuesday")
elif day == 3:
print("Wednesday")
else:
print("Other day")day = 2
match day:
case 1:
print("Monday")
case 2:
print("Tuesday")
case 3:
print("Wednesday")
case _:
print("Other day")Both do the same work for a value of 2, which prints Tuesday. The if version repeats day == on every line. The match version names the value once, at the top, and every case then refers to it. That is the reason a match reads more easily when there are many fixed values.
Common mistakes and the messages Python gives you
Each of these mistakes has a recognisable message or behaviour. Some of them give no error at all, and those are the hardest to find.
Mistake 1: a number typed as text. input() returns text. You type 1 and the program compares the text "1" with the number 1 in case 1:. Nothing matches, so the default prints. The program looks broken, but no error appears. Convert the input with int(input()) before the match, or compare with case "1":.
Mistake 2: a list of values without |. You write case 6, 7: and expect "either 6 or 7". Python reads the comma as a sequence pattern, which describes a list of two items. The subject here is the number 6, which is not a sequence, so the case never matches. This prints weekday for the value 6, and no error appears. Write case 6 | 7: instead.
Mistake 3: or in a case. You write case 1 or 2:. Python reports:
File "main.py", line 3
case 1 or 2:
^^
SyntaxError: invalid syntaxUse | in patterns. or is for conditions in if statements.
Mistake 4: a bare name in a case. You write case RED: for an enum member. The name is captured and matches everything, as shown in the enum section. Python reports SyntaxError: name capture 'RED' makes remaining patterns unreachable when more cases follow it. Use case Light.RED:.
Mistake 5: a variable as a case label. You write case limit: where limit holds 5. The name is a capture, not a comparison with the variable. The case matches every value. This program prints matched, limit name captured: 3 for the value 3:
limit = 5
match 3:
case limit:
print("matched, limit name captured:", limit)To compare with a variable, use a guard, as in case n if n == limit:.
Mistake 6: a default that is not last. You write case _: above other cases. Python reports:
SyntaxError: wildcard makes remaining patterns unreachableThe default matches everything, so the cases after it could never run. Move case _: to the bottom.
Mistake 7: a missing colon. You write case 1 without the colon. Python reports SyntaxError: expected ':', with the caret after the 1. Every case line ends with a colon, as every if does.
Mistake 8: no default for a function. You write a match inside a function, with a return in each case and no default. A value that matches no case reaches the end of the function, and Python returns None without any message. Add a case _: with a return, or a return after the match.
Mistake 9: a None value that you did not expect. A variable that should hold text holds None instead, because a function forgot to return. The match runs the default and carries on. Check the variable before the match when None would be a bug, as in if cmd is None:.
Debugging walkthrough: a menu that never matches
Suppose a player types 1 in a menu, and the program prints Please choose 1, 2 or 3 every time. The code looks like this:
choice = input()
match choice:
case 1:
print("Starting the game")
case 2:
print("Help")
case _:
print("Please choose 1, 2 or 3")Step 1: write down the value. The player typed 1, so input() gives the text "1".
Step 2: find the matching case. Python compares the text "1" with case 1:. The number 1 is not the text "1", so there is no match.
Step 3: run the default. The default prints the message the player saw.
The bug is the type of the value. The fix is to convert the text before the match:
choice = int(input())Now choice is the number 1, and case 1: fits. Type each choice from 1 to 3 and confirm the output. The default line should appear only for other numbers. This is the most common match bug for beginners, and it is easy to recognise once you know to check the type of the value.
Interview questions about match
Interviewers ask about match to see whether you can organise many choices and whether you know its limits. Three ideas come up often.
The first is the mapping pattern. If a problem maps a fixed set of codes to answers, a match or a dictionary is clear and fast. Examples include a day number to a name, a status code to a message, or a character to a token type. A dictionary lookup takes about the same time for any key, and the keys must be known in advance.
The second is the state machine. A program that moves between a few named states, such as a traffic light or a vending machine, often keeps the current state in an enum and uses a match to decide what happens next. This is a common interview design. Each case says what the machine does in that state. Say out loud that Python does not check every state for you, and explain how you would check it with a test table or a type checker.
The third is the no-fall-through question. An interviewer may show you a match and ask what it prints when a value matches two cases. The answer is that the first matching case runs, and the rest are not tried. A common mistake is to assume that Python keeps running the cases below the match, as some other languages do. Trace the code from the top, and stop at the end of the first block that fits.
Real-world uses
match appears wherever a program handles commands. A chat program reads a command word and runs the matching action. A parser for a file format reads a token, decides what kind of token it is, and handles it. A vending machine reads a button code and dispenses the item. A game menu handles arrow keys and the Enter key. Python's structural patterns make it short to handle messages of different shapes, such as a request that is either a plain text or a list of fields.
When you read a large match in real code, check three things: whether the value has the type the cases expect, whether the default handles the unexpected, and whether an enum match covers every member. Those checks catch most match bugs before they reach users.
Practice
Each exercise has an answer below it. Try it on your own first, then read the answer to compare. Type the sample inputs when your program asks for them.
- Read a number from 1 to 4 and print the name of the season:
1is winter,2is spring,3is summer and4is autumn. Printunknownfor anything else. Use amatchwith a default.
Show the answer
n = int(input())
match n:
case 1:
print("winter")
case 2:
print("spring")
case 3:
print("summer")
case 4:
print("autumn")
case _:
print("unknown")Each case prints once, and the default handles every other number, including 0 and 9.
- Write a version of exercise 1 that stores the season name in a variable and prints it once at the end.
Show the answer
n = int(input())
match n:
case 1:
season = "winter"
case 2:
season = "spring"
case 3:
season = "summer"
case 4:
season = "autumn"
case _:
season = "unknown"
print(season)Every case assigns season, including the default, so the print always has a value to show.
- Read a month number from 1 to 12 and print the number of days in that month, treating February as 28. Use
|so that the 30-day months share one case, and print30for them. Print31for the 31-day months, andnot a monthfor anything else.
Show the answer
m = int(input())
match m:
case 2:
print(28)
case 4 | 6 | 9 | 11:
print(30)
case 1 | 3 | 5 | 7 | 8 | 10 | 12:
print(31)
case _:
print("not a month")The | line lists several values for one block, which is the job that stacked labels do in other languages.
- Predict the output of this program, then run it to check. Explain why the last line prints.
x = 5
match x:
case 5:
print("five")
case 6:
print("six")
case 7:
print("seven")
print("end")Show the answer
The output is five and then end. The value 5 matches case 5, and that block prints five. Python does not run the cases below it, so six and seven do not print. The last line is outside the match, so it always runs.
- Write a program that reads one character and prints
digitfor the characters0to9,letterfor the lettersatozin either case, andotherfor anything else. Explain why amatchwith one case per character is a poor choice here.
Show the answer
ch = input()
if "0" <= ch <= "9":
print("digit")
elif "a" <= ch.lower() <= "z":
print("letter")
else:
print("other")A match would need ten cases for the digits and 26 for the letters, and each case would repeat the same block. A pattern cannot describe a range, so an if ladder with comparisons on the character is the clearer choice. The .lower() call makes A count as a letter.
- A calculator
matchhas a case for/that divides without a guard. Add a guard so that dividing by zero printsCannot divide by zero. Explain which line must come first.
Show the answer
def calculate(x, op, y):
match op:
case "+":
return x + y
case "/":
if y == 0:
return "Cannot divide by zero"
return x / y
case _:
return "Unknown operator"
print(calculate(7, "/", 0))
print(calculate(7, "/", 2))Cannot divide by zero
3.5The test must come before the division. Python runs the division when the line is reached, so the check has to be above it.
- Write a program with an enum
Cointhat has the membersPENNY,NICKEL,DIMEandQUARTER. Store one coin in a variable and use amatchto set its value in cents: 1, 5, 10 and 25. Print the value.
Show the answer
from enum import Enum
class Coin(Enum):
PENNY = 1
NICKEL = 2
DIME = 3
QUARTER = 4
coin = Coin.DIME
match coin:
case Coin.PENNY:
cents = 1
case Coin.NICKEL:
cents = 5
case Coin.DIME:
cents = 10
case Coin.QUARTER:
cents = 25
print(cents)The output is 10. The enum values 1 to 4 are only labels here. The match gives the cents. A shorter version would store the cents in the enum values, but this exercise asks for the match.
- Explain why
case limit:does not compare with a variable namedlimit, and write the same test as a guard that compares with a variable.
Show the answer
A name in a case is a capture. It matches any value and gives that value the name. So case limit: matches everything, and limit is set to the value being matched. To compare, use a guard that names the value first:
limit = 5
score = 5
match score:
case s if s == limit:
print("equal to the limit")
case _:
print("different")With score set to 5, the output is equal to the limit.
- A program matches on a string read from the user. Explain why Python does not crash when the value is
None, and write the two lines that turn aNoneinto an empty string before the match.
Show the answer
Python's match compares the value with each case in turn, and None does not equal any text, so it reaches the default. The program keeps running. If you want an empty string instead, check for None first:
cmd = None
if cmd is None:
cmd = ""
match cmd:
case "stop":
print("Halting")
case _:
print("Unknown command")With an empty string, the match reaches case _: and prints Unknown command. The test uses is None, which is the normal way to check for None.
- Debug this program. It should print
weekendfor days 6 and 7, andweekdayfor the others, but it printsweekdayfor 6 too. Find the bug and fix it.
day = 6
match day:
case 6, 7:
print("weekend")
case _:
print("weekday")Show the answer
The bug is the comma. In a case, a comma makes a sequence pattern, which describes a list of two items. The number 6 is not a list, so nothing matches it except the default. The fix uses |, which means "either value":
day = 6
match day:
case 6 | 7:
print("weekend")
case _:
print("weekday")This version prints weekend for both days.
- Write a
matchthat converts an operator character,+,-,*or/, into the wordadd,subtract,multiplyordivide. Returnunknownfor anything else. Use a function that returns the word.
Show the answer
def operator_word(op):
match op:
case "+":
return "add"
case "-":
return "subtract"
case "*":
return "multiply"
case "/":
return "divide"
case _:
return "unknown"
print(operator_word("*"))
print(operator_word("%"))The output is multiply and then unknown. The default is needed, or the function would return None for any other character.
- Explain in plain words why a Python
matchnever needs abreakto stop it. Then say how the vowel example would change if each vowel needed its ownprintline.
Show the answer
When a case's block ends, the match ends, so there is nothing to fall into. A break would only be needed in a language that keeps running later labels, and Python does not have that behaviour. If each vowel printed its own message, the | line would have to go. Each vowel would get its own case, such as case "a": with its own print, and the cases would not share a block.
What you now know
- A
matchcompares one value with patterns from the top and runs the block of the first pattern that fits. - A
matchdoes not fall through. When a block ends, the wholematchends, so nobreakis needed. - Use
|to list several values for one block. A comma in a case means a sequence pattern, andoris a syntax error. - The default is
case _:, and it must be the last case. - A bare name in a case captures any value. Enum members must be written with the enum name, such as
Light.RED. - A guard, written
case x if ...:, adds a condition, which lets amatchtest ranges. input()returns text, so convert it withint(...)before comparing with number cases.Nonedoes not crash amatch. It reaches the default, so check for it when that would be a bug.