match statements (intro)

Beginner30 min

Replace long if/elif chains with match, using literal, capture, wildcard and alternative patterns plus guards.

What you will learn
  • Write a match statement with literal and wildcard patterns
  • Combine patterns with | and capture values with a name
  • Add an if guard to a case
  • Decide when match is clearer than if/elif
On this page

Requires version 3.10 or later A long if/elif chain that compares one value against many possibilities gets noisy. Python 3.10 added the match statement, called structural pattern matching, to say the same thing more directly (PEP 634 is the specification, PEP 636 the tutorial). It does a lot more than a switch in other languages, but this lesson covers the everyday core. Everything in this lesson needs Python 3.10 or later.

Literal patterns, alternatives and guards

The word after match is the subject. Python tries each case from top to bottom and runs the block of the first pattern that fits, then leaves the whole match. There is no fall-through, so you never write break.

The examples loop over a few values so you can see every case run. The text "200 302 404 503 418".split() makes a list of five words, and int(text) turns each into a number.

match_basics.py
for text in "200 302 404 503 418".split():
status = int(text)
match status:
case 200:
print(status, "ok")
case 301 | 302:
print(status, "redirect")
case 404:
print(status, "not found")
case code if code >= 500:
print(status, "server error", code)
case _:
print(status, "other")
Output
200 ok
302 redirect
404 not found
503 server error 503
418 other

The patterns above:

  • Literal pattern (case 200): matches when the subject equals that number, string, True, False or None.
  • Or-pattern (case 301 | 302): matches if either alternative matches.
  • Capture pattern (case code): a bare name matches anything and binds it to that name, here code.
  • Guard (if code >= 500): an extra condition after the pattern. The case runs only when the pattern fits and the guard is truthy.
  • Wildcard (case _): matches anything and binds nothing. Use it last, as the “else”.

Matching shapes

Patterns can also describe the shape of a sequence. This is where match pulls ahead of if. A text-adventure command parser is a good example, since a command is a list of words. command.split() turns "go north" into the list ["go", "north"], and a sequence pattern with the same square brackets matches it.

adventure.py
for line in "look;go north;go up;dance;quit".split(";"):
print(">", line)
match line.split():
case ["quit"]:
print("Goodbye.")
case ["look"]:
print("You see a dusty room.")
case ["go", ("north" | "south" | "east" | "west") as direction]:
print("You walk", direction + ".")
case ["go", _]:
print("You cannot go that way.")
case _:
print("I don't understand", repr(line) + ".")
Output
> look
You see a dusty room.
> go north
You walk north.
> go up
You cannot go that way.
> dance
I don't understand 'dance'.
> quit
Goodbye.

A sequence pattern like ["go", _] matches a two-item list whose first item is the string "go"; the second can be anything. Each name in the pattern is bound as the match happens. ("north" | "south" | "east" | "west") as direction matches any of four literals and also keeps the matched value under a name with as.

Order matters, as in if/elif. ["go", _] sits after the direction case, so valid directions are handled first.

Capturing the rest of a sequence

A starred name in a pattern captures any number of remaining items as a list, and the pattern [] matches an empty list, which is what "".split() gives.

adventure_take.py
for line in "take lamp;take a b;".split(";"):
print(">", line)
match line.split():
case ["take", item]:
print("You pick up the", item + ".")
case ["take", *items] if items:
print("One thing at a time:", ", ".join(items) + ".")
case []:
print("Say something.")
case _:
print("I don't understand.")
Output
> take lamp
You pick up the lamp.
> take a b
One thing at a time: a, b.
>
Say something.

["take", item] matches exactly two words and captures the second as item. ["take", *items] uses a star to capture the remaining words, and the guard if items makes sure at least one word follows. The last line of the input is empty, so "".split() is [] and the [] case answers.

When to use match

Reach for match when you are branching on the value or shape of a single subject: command parsers, message types, status codes, small tokenisers. Keep if/elif for conditions that compare different things or involve ranges and calculations. Both are fine for a few branches, so choose whichever reads better. The pattern language goes much deeper (class patterns, mappings, nested shapes), and an advanced lesson picks that up.

Common pitfalls

  • Treating a bare name as a constant to compare against. case MISSING: does not test for equality with a variable named MISSING. It captures anything into a new name MISSING. To match a named constant, use a dotted name such as Status.MISSING, which is compared by value.
  • Putting a capture or _ before other cases. A pattern that matches everything makes later cases unreachable. Python refuses to compile that with a SyntaxError, for example “name capture ‘x’ makes remaining patterns unreachable”.
  • Forgetting that a string subject is not a list of words. match "go north": with case ["go", _] does not match, because strings are deliberately not treated as sequences by sequence patterns. Split the text first.
  • Using it on Python older than 3.10. match is a SyntaxError there. Check python --version.
  • Expecting fall-through or a break. After one case runs, the match is done.
ExerciseWeekday or weekend

Use match to print weekday for the numbers 1 to 5 and weekend for 6 and 7. Loop over the numbers 1 to 7 and print each number followed by its label. Use an or-pattern for the weekend.

Input: none. Expected output: seven lines from 1 weekday to 7 weekend.

Show solution
weekday_match.py
for day in range(1, 8):
match day:
case 6 | 7:
print(day, "weekend")
case _:
print(day, "weekday")
Output
1 weekday
2 weekday
3 weekday
4 weekday
5 weekday
6 weekend
7 weekend

The or-pattern 6 | 7 catches both weekend days, and the wildcard handles everything else.

ExerciseText-adventure command parser

Extend the parser with two new commands: "drop <item>", which prints You drop the <item>., and "inventory", which prints You carry nothing.. Test with the commands drop lamp, inventory and drop on its own, which should fall through to the “I don’t understand” case.

Input: the commands drop lamp, inventory, drop. Expected output:

You drop the lamp.
You carry nothing.
I don't understand 'drop'.
Show solution
adventure_more.py
for line in "drop lamp;inventory;drop".split(";"):
match line.split():
case ["inventory"]:
print("You carry nothing.")
case ["drop", item]:
print("You drop the", item + ".")
case _:
print("I don't understand", repr(line) + ".")
Output
You drop the lamp.
You carry nothing.
I don't understand 'drop'.

["drop", item] needs exactly two words, so a bare drop is one word and skips to the wildcard. In the full parser, add these two cases anywhere before case _.

Summary

  • Requires version 3.10 or later match subject: followed by case pattern: blocks runs the first case that fits; there is no fall-through.
  • Literals compare by value, | offers alternatives, a bare name captures, _ is the wildcard, and if adds a guard.
  • Sequence patterns such as ["go", direction] match by shape and bind names, which makes command parsing tidy.

That completes the control-flow module. Next comes collections, starting with lists.