Match case is one of the most revolutionary additions to Python since version 3.10. Inspired by languages like Rust, Scala, and Haskell, structural pattern matching lets you write cleaner, more expressive, and safer code when making decisions based on data structure.
Unlike traditional if-elif-else chains, match case doesn't just compare values — it destructures data, checks types, matches nested patterns, and captures variables automatically. This complete guide covers everything you need to master this powerful feature.
What Is Match Case?
The match statement is a new conditional construct introduced in Python 3.10 through PEP 634. It lets you compare a value against a series of structured patterns. Each pattern can include literals, variables, types, sequences, mappings, and even complex nested patterns.
The basic syntax is straightforward:
match value:
case pattern_1:
# action for pattern_1
case pattern_2:
# action for pattern_2
case _:
# wildcard pattern (default)
The underscore _ in the last case is the wildcard pattern, matching any value not caught by previous cases. The official Python documentation covers the full specification in PEP 634.
Why Use Match Case?
Before match case, handling multiple conditions and data formats usually resulted in lengthy if-elif-else chains or dispatch dictionaries. Match case solves this elegantly:
- Readability: Code becomes more declarative and easier to follow
- Safety: The compiler checks whether all cases are covered
- Expressiveness: Nested patterns, guards, and destructuring in a single statement
- Performance: Optimized C implementation in CPython
Basic Syntax and First Examples
Let's start with a simple terminal command handler:
def process_command(command):
match command:
case "quit":
return "Shutting down..."
case "help":
return "Available commands: quit, help, version"
case "version":
return "Python Pattern Matching v1.0"
case _:
return f"Unknown command: {command}"
print(process_command("help"))
print(process_command("quit"))
print(process_command("invalid"))
This example cleanly replaces an if-elif-else chain. But match case goes far beyond simple literal comparison.
Literal Matching
Match case can compare against any Python literal, including integers, strings, booleans, and None:
def classify_response(response):
match response:
case True:
return "Positive response"
case False:
return "Negative response"
case None:
return "No response"
case 42:
return "Universal answer!"
case "maybe":
return "Undecided response"
case _:
return "Unrecognized response"
print(classify_response(42))
print(classify_response(None))
According to PEP 635 (Motivation and Rationale), literal matching forms the foundation of pattern matching and provides much cleaner syntax than traditional switch-case in other languages.
Variable Capture
One of match case's most useful features is the ability to capture values into variables during matching:
def personalized_greeting(name):
match name:
case "Admin":
return "Welcome, Administrator!"
case other_name:
return f"Hello, {other_name}!"
print(personalized_greeting("Admin"))
print(personalized_greeting("Maria"))
In this example, other_name captures any value that isn't "Admin". This becomes extremely powerful when combined with other patterns.
Sequence Matching
Match case truly shines when used to destructure sequences like lists and tuples. You can check length and capture specific elements simultaneously:
def analyze_coordinates(point):
match point:
case [x, y]:
return f"2D point at ({x}, {y})"
case [x, y, z]:
return f"3D point at ({x}, {y}, {z})"
case [x, y, z, w]:
return f"4D point at ({x}, {y}, {z}, {w})"
case _:
return "Invalid coordinate format"
print(analyze_coordinates([10, 20]))
print(analyze_coordinates([1, 2, 3]))
print(analyze_coordinates([]))
You can also use * to capture the rest of the sequence, similar to *args:
def first_and_rest(items):
match items:
case [first, *rest]:
return f"First: {first}, Rest: {rest}"
case []:
return "Empty list"
print(first_and_rest([1, 2, 3, 4, 5]))
print(first_and_rest([]))
The official PEP 636 tutorial demonstrates several practical sequence matching examples, from data analysis to natural language processing.
Dictionary Matching
One of match case's most practical applications is matching against dictionaries, especially when dealing with JSON API responses:
def process_api_response(response):
match response:
case {"status": "ok", "data": data}:
return f"Data received: {data}"
case {"status": "error", "message": msg}:
return f"API error: {msg}"
case {"status": "error"}:
return "Unknown API error"
case _:
return "Invalid response"
api_ok = {"status": "ok", "data": {"username": "john", "id": 1}}
api_error = {"status": "error", "message": "404 Not Found"}
print(process_api_response(api_ok))
print(process_api_response(api_error))
Match case automatically checks whether the required keys exist and captures the corresponding values. Our guide on Python dictionaries provides a solid foundation for mastering this pattern.
Class and Object Matching
Match case can destructure objects directly, checking the class and capturing attributes:
from dataclasses import dataclass
@dataclass
class User:
name: str
email: str
plan: str
@dataclass
class Admin:
name: str
email: str
level: int
def describeuser(user):
match user:
case Admin(name=name, level=level):
return f"Admin {name} (level {level})"
case User(name=name, plan="premium"):
return f"Premium user: {name}"
case User(name=name):
return f"Regular user: {name}"
case :
return "Unknown user type"
user1 = User("Ana", "[email protected]", "premium")
user2 = Admin("Carlos", "[email protected]", 3)
user3 = User("John", "[email protected]", "basic")
print(describe_user(user1))
print(describe_user(user2))
print(describe_user(user3))
This feature enables much more expressive object-oriented code. The Real Python tutorial explores class matching and named patterns in depth.
Nested Patterns
The true power of match case emerges when you combine multiple patterns in a nested fashion:
def analyze_order(order):
match order:
case {"type": "food", "item": item, "quantity": qty}:
return f"Food order: {qty}x {item}"
case {"type": "drink", "item": item, "quantity": qty, "size": size}:
return f"Drink: {size} {item} ({qty}x)"
case {"type": "electronics", "item": item}:
return f"Electronics: {item}"
case {"type": kind, "item": item}:
return f"Generic {kind} item: {item}"
case _:
return "Invalid order"
order1 = {"type": "food", "item": "Pizza", "quantity": 2}
order2 = {"type": "drink", "item": "Juice", "quantity": 1, "size": "Large"}
order3 = {"type": "electronics", "item": "Bluetooth Headphones"}
print(analyze_order(order1))
print(analyze_order(order2))
print(analyze_order(order3))
The GeeksforGeeks article on match case provides more nested pattern examples applied to real-world problems.
Guards (Case ... If)
Sometimes you need extra conditions beyond pattern matching. That's where guards come in:
def classify_number(n):
match n:
case _ if n < 0:
return f"{n} is negative"
case _ if n == 0:
return "Zero"
case _ if n < 10:
return f"{n} is a single digit"
case _ if n < 100:
return f"{n} is between 10 and 99"
case _:
return f"{n} is 100 or greater"
print(classify_number(-5))
print(classify_number(0))
print(classify_number(7))
print(classify_number(42))
print(classify_number(1000))
Guards are evaluated after pattern matching and enable complex conditional logic much more cleanly than nested ifs.
OR Matching with |
You can use the pipe operator (|) to combine multiple patterns into a single case:
def day_of_week(day):
match day:
case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
return "Weekday"
case "saturday" | "sunday":
return "Weekend"
case _:
return "Invalid day"
print(day_of_week("monday"))
print(day_of_week("saturday"))
print(day_of_week("invalid"))
This syntax makes the code much more compact than scattered OR conditions.
Enum Matching
Match case pairs perfectly with Enum to model finite states:
from enum import Enum, auto
class OrderStatus(Enum):
PENDING = auto()
PAID = auto()
SHIPPED = auto()
DELIVERED = auto()
CANCELLED = auto()
def order_statustext(status):
match status:
case OrderStatus.PENDING:
return "Order awaiting payment"
case OrderStatus.PAID:
return "Order paid, awaiting shipment"
case OrderStatus.SHIPPED:
return "Order shipped for delivery"
case OrderStatus.DELIVERED:
return "Order delivered successfully"
case OrderStatus.CANCELLED:
return "Order cancelled"
case :
return "Unknown status"
print(order_status_text(OrderStatus.PENDING))
print(order_status_text(OrderStatus.DELIVERED))
Practical Applications
Let's explore real-world scenarios where match case excels:
1. CLI Command Parsing
import sys
def parsecommand():
args = sys.argv[1:]
match args:
case ["search", *terms]:
return f"Searching for: {' '.join(terms)}"
case ["download", url]:
return f"Downloading: {url}"
case ["config", key, value]:
return f"Setting {key} = {value}"
case []:
return "No command provided. Use --help for assistance."
case :
return "Unrecognized command. Use --help for assistance."
2. Error Handling with Specific Types
def handle_error(error):
match error:
case ValueError(msg):
return f"Value error: {msg}"
case TypeError(msg):
return f"Type error: {msg}"
case ConnectionError(msg):
return f"Connection error: {msg}"
case Exception(msg):
return f"Generic error: {msg}"
case _:
return "Unknown error"
try:
result = 10 / 0
except Exception as e:
print(handle_error(e))
The LearnPython.com tutorial shows how match case can dramatically simplify error handling and input validation in real applications.
3. Input Data Validation
def validate_input(data):
match data:
case {"name": str() as name, "age": int() as age} if age >= 18:
return f"User {name} validated (adult)"
case {"name": str() as name, "age": int() as age}:
return f"User {name} is a minor"
case {"name": str()}:
return "Age not provided"
case {}:
return "Incomplete data"
case _:
return "Invalid data format"
valid_data = {"name": "Ana", "age": 25}
minor_data = {"name": "John", "age": 15}
no_age_data = {"name": "Carlos"}
print(validate_input(valid_data))
print(validate_input(minor_data))
print(validate_input(no_age_data))
Match Case vs If-Elif-Else
When should you use match case over if-elif-else?
| Situation | Match Case | If-Elif-Else |
|---|---|---|
| Multiple literal comparisons | ✅ Excellent | ✅ Good |
| Sequence destructuring | ✅ Excellent | ❌ Poor |
| Type and attribute checking | ✅ Excellent | ⚠️ Fair |
| Arbitrary complex conditions | ⚠️ Fair | ✅ Excellent |
| Nested patterns | ✅ Excellent | ❌ Poor |
| Simple boolean comparison | ❌ Overkill | ✅ Excellent |
The official Python control flow documentation provides additional guidelines on when each approach is most appropriate.
Limitations and Caveats
Despite its power, match case has some limitations:
- Python 3.10+: Legacy code won't support it — always check compatibility
- Order matters: The first matching case executes, so organize from most to least specific
- Not an if-else replacement: For simple boolean conditions, if-else is still more appropriate
- No break needed: Unlike C's switch-case, there's no fall-through — only one case executes
Best Practices
Follow these recommendations to get the most out of match case:
1. Always include a wildcard case
Use case _: at the end to catch unexpected values:
match value:
case 1:
print("One")
case 2:
print("Two")
case _:
print("Other value")
2. Order from specific to generic
match data:
case [x, y, z]: # Most specific first
pass
case [x, y]: # Less specific after
pass
case _: # Generic last
pass
3. Use data classes for cleaner patterns
Combining @dataclass with match case produces the most expressive code. Understanding function fundamentals is essential — our Python functions guide covers everything you need to know.
Match Case in the Python Ecosystem
Match case is already being adopted by popular libraries. FastAPI uses pattern matching for advanced routing. Data processing frameworks like Pydantic v2 have incorporated pattern-matching-inspired validation. The PEP 634 specification serves as the foundation for these implementations.
Conclusion
Match case is a transformative tool in modern Python. It makes code more readable, safer, and more expressive, especially when dealing with complex data structures. While it doesn't completely replace if-elif-else, pattern matching offers a vastly superior alternative for a wide range of scenarios.
Practice the examples in this guide, experiment with creating your own patterns, and you'll find that match case quickly becomes one of your favorite Python features.
Keep exploring Universo Python for more content on modern Python and development best practices!