Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Python 3.10 and later implement switch-style branching with the match/case statement, formally called structural pattern matching. It handles ordinary value choices, multiple alternatives, conditions, and the shape of sequences, mappings, and objects. Python 3.9 and older cannot parse this syntax, so use if/elif or dictionary dispatch there.

def describe_status(status):
    match status:
        case 200:
            return "OK"
        case 400 | 401:
            return "Request or authorization problem"
        case 404:
            return "Not found"
        case _:
            return "Other status"

Cases are checked from top to bottom. The first pattern that matches (and whose optional guard is true) runs; Python does not fall through into later cases.

Does Python have switch-case?

Yes, in practical terms. Python 3.10 added match/case, a language feature that covers the role commonly filled by switch in C-like languages while also matching data structure. The current language reference documents its syntax and semantics at docs.python.org/3/reference/compound_stmts.html. The Python 3.10 tutorial introduces it as an expression compared with successive patterns in case blocks (official tutorial).

A match statement evaluates its subject once, then tests each case in source order. It is not a function that returns a value automatically, so return or assign inside each suite as needed.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Basic match/case syntax

Match exact values

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Other error"

for code in (200, 404, 418, 500):
    print(code, http_error(code))

Literal patterns such as integers and strings are compared with equality. The special literals None, True, and False use identity semantics. The underscore is not a value to compare: it is the wildcard pattern.

Add the default branch

case _: catches anything not handled earlier. It is the switch-style default branch:

def classify_status(status):
    match status:
        case 200:
            return "success"
        case 400 | 401:
            return "client or authentication problem"
        case _:
            return "unhandled status"

A wildcard is optional. If no case matches and no case _: appears, the match statement does nothing and execution continues with the next statement.

Combine several values with |

An OR pattern sends several alternatives to one suite:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
def permission_message(code):
    match code:
        case 401 | 403:
            return "Authentication or permission problem"
        case 404:
            return "Resource not found"
        case _:
            return "Another response"

Each alternative must bind compatible names if you use captures. For example, alternatives that bind different sets of names cannot form one valid OR pattern.

Guards: add a condition after a pattern

A guard is an if condition attached to a case. Python first checks the pattern, then evaluates the guard. If the guard is false, matching continues with the next case.

def describe_number(value):
    match value:
        case int(number) if number > 0:
            return "positive integer"
        case int(number) if number < 0:
            return "negative integer"
        case 0:
            return "zero"
        case _:
            return "not an integer"

Put more specific patterns before broad ones. A broad pattern or wildcard placed first makes later cases unreachable in practice.

Structural pattern matching

Match a sequence and unpack fields

Patterns can validate a sequence’s length and contents while binding selected elements:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
def handle_command(text):
    match text.split():
        case ["quit"]:
            return "Goodbye"
        case ["go", direction]:
            return f"Moving {direction}"
        case ["get", item]:
            return f"Taking {item}"
        case _:
            return "Unrecognized command"

print(handle_command("go north"))
print(handle_command("get lantern"))

The ["go", direction] pattern requires a two-item sequence whose first item is the string "go"; the second item is bound to direction. A starred name can absorb the remainder, for example ["run", *arguments].

Match mappings

Mapping patterns test required keys and bind their values. Extra keys are allowed unless you separately reject them:

def event_text(event):
    match event:
        case {"type": "login", "user": user}:
            return f"{user} logged in"
        case {"type": "error", "code": code, "message": message}:
            return f"Error {code}: {message}"
        case _:
            return "Unknown event"

Match class instances

Class patterns can inspect attributes declared by the class’s positional pattern configuration or by keyword:

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

def location(point):
    match point:
        case Point(0, 0):
            return "origin"
        case Point(x, 0):
            return f"x-axis at {x}"
        case Point(0, y):
            return f"y-axis at {y}"
        case Point(x, y):
            return f"({x}, {y})"
        case _:
            return "not a point"

This structural capability is the main reason to choose match over a long list of equality tests. PEP 634 specifies the normative behavior (PEP 634), while the practical tutorial is covered in PEP 636.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Capture names versus constants

A bare name in a pattern is a capture, not a comparison with an existing variable:

command = "quit"

match command:
    case command:       # captures any value; effectively matches everything
        print("This is not a constant comparison")

Use a literal or a qualified name for a constant:

from enum import Enum

class Commands(Enum):
    QUIT = "quit"
    HELP = "help"

def execute(command):
    match command:
        case Commands.QUIT:
            return "Goodbye"
        case Commands.HELP:
            return "Help text"
        case _:
            return "Unknown command"

The qualified enum members are value patterns, so they test the subject rather than capturing it. PEP 634 explains this distinction and the other pattern forms in detail.

Does match/case fall through?

No. Once a pattern matches and its guard (if any) succeeds, only that case suite executes. Execution resumes after the entire match statement. To share behavior, put alternatives in one OR pattern or call a common function; do not add C-style break statements.

def access_message(role):
    match role:
        case "owner" | "admin":
            return "Full access"
        case "editor":
            return "Edit access"
        case _:
            return "Read-only access"

Choosing match, if/elif, or a dictionary

Need Recommended form Reason
A few arbitrary boolean, range, or compound conditions if/elif Conditions are direct and familiar.
Exact choices or several values sharing an action match/case on Python 3.10+ Literal patterns, OR patterns, wildcard, and guards make the cases explicit.
Branching on sequence, mapping, or object shape while extracting fields match/case The pattern validates structure and binds components in one operation.
Python 3.9 or older if/elif or dictionary dispatch Older interpreters cannot parse match syntax.
Simple key-to-value or key-to-function lookup Dictionary Compact direct dispatch can be clearer than a statement.

Dictionary dispatch example

def say_hello():
    return "Hello"

def say_goodbye():
    return "Goodbye"

actions = {
    "hello": say_hello,
    "goodbye": say_goodbye,
}

def run(action):
    function = actions.get(action)
    return function() if function else "Unknown action"

A dictionary is an alternative technique, not an implementation of match semantics. It does not inherently match structure, apply ordered guards, or provide a wildcard pattern.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Python-version requirements and migration

The grammar for match/case was added in Python 3.10. A 3.9 (or earlier) interpreter fails while parsing the file, before your code can choose a fallback. Check the runtime that actually executes your application:

python --version
python3 --version

If you publish a package, set its minimum Python version in the project metadata and CI configuration, or keep the implementation in if/elif and dictionary forms until all supported runtimes are 3.10 or newer. Do not hide 3.10 syntax in a module that an older interpreter must import.

Common errors and reliable fixes

A bare constant name matches everything

Symptom: a supposedly specific case runs for every input, or the compiler reports a capture that makes later patterns unreachable.

Fix: replace case RED: with a literal such as case "red": or a qualified constant such as case Colors.RED:.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Expecting fall-through

Symptom: code expects two case suites to run.

Fix: combine values with |, or call shared code from one case. No implicit fall-through exists.

No action for an unmatched value

Symptom: a function returns None or silently continues.

Fix: add case _: with an explicit return, error, log, or other policy. Omitting it is valid only when doing nothing is intentional.

Syntax error on an older interpreter

Symptom: Python 3.9 or earlier reports invalid syntax at match.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: upgrade the runtime or rewrite the branch with if/elif or a dictionary. A compatibility shim cannot make the new grammar parse.

Cases are in the wrong order

Symptom: a later, more specific case never executes.

Fix: place narrow literal, sequence, mapping, or class patterns before broad patterns and guards; keep case _: last.

Relying on failed partial-match bindings

Symptom: code reads a name after a pattern failed partway through.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: treat bindings from failed partial matches as unspecified. Keep subsequent logic independent of whether such names happen to exist or retain an earlier value; this is the guidance in the current language reference.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Testing and performance considerations

Test every intended case, the wildcard path, wrong types, malformed structures, and boundary values for guards. For command parsers, include extra tokens and missing fields. Static analysis can catch unreachable or irrefutable patterns, but unit tests should verify the behavior your application requires.

Python’s specification defines matching behavior, not a universal speed advantage over if/elif or dictionary lookup. Choose the clearest form and benchmark the complete workload on the Python version and data your application uses when performance matters. PEP 622 discusses the background and rationale for the design (PEP 622).

Or skip the browser setup

If your Python application also needs website screenshots for tests, documentation, or event-driven workflows, ScreenshotNeo provides a one-request API instead of requiring you to configure a headless browser. See the ScreenshotNeo documentation for all parameters.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Before capture, cookie or consent banners, newsletter popups, and chat widgets are removed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; the response identifies the page verdict and billing result in X-Page-Verdict and X-Billed headers. ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Frequently Asked Questions

Can a match statement return a value directly?

No. A match statement is a statement, so each case must return, assign, yield, or otherwise produce the result explicitly; wrap it in a function when you want a returned value.

Can patterns match types as well as values?

Yes. Class patterns such as int(number) can check a type and bind its value, and custom class patterns can inspect declared attributes.

What happens when a guard is false?

That case is skipped and Python continues testing the following cases in source order.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Are dictionary keys in mapping patterns required to be the only keys?

No. A mapping pattern requires the keys it names but permits additional keys unless your code separately checks for an exact mapping.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.