ScreenshotNeo

BlogHow-to

How to Implement Switch-Case in Python

Use Python’s match/case for switch-style branching in Python 3.10+, or choose if/elif or dictionary dispatch for older versions and simpler lookups.

By the ScreenshotNeo team29 September 20268 min read

How to Implement Switch-Case in Python

Direct answer: Python 3.10 and later support switch-style branching with match/case, formally called structural pattern matching. Use it for exact choices, alternative values, and branching on the shape of data. For Python 3.9 and older, use if/elif or a dictionary dispatch: older interpreters cannot parse match syntax.

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"

print(describe_status(404))  # Not found

Unlike a simple C-style switch, Python’s match can check and unpack sequences, mappings, and class instances. Cases are considered in order; at most one case body runs. The Python language reference documents the statement’s syntax and behavior in the match statement reference.

1. Check your Python version

Pattern matching was introduced in Python 3.10. Check the interpreter used to run your program:

python --version
# or, where python points to another interpreter:
python3 --version

If the version is 3.10 or newer, you can use match. If the project must run on Python 3.9 or earlier, use the compatible alternatives below or deliberately raise the project’s minimum supported version. A compatibility condition around a match statement does not help: the older interpreter must parse the file before it can evaluate a version check.

2. Write a basic match/case statement

A match statement evaluates its subject once and tests patterns from top to bottom. Here is a runnable example for HTTP-like status values:

Python checks cases in order and executes only the first matching case body.
Python checks cases in order and executes only the first matching case body.
def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 401:
            return "Unauthorized"
        case 403:
            return "Forbidden"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Other status"

for status in (200, 401, 404, 503):
    print(status, http_error(status))

The final case _: is a wildcard: it matches anything not already handled. It is the usual default branch. A wildcard is optional. If no case matches and no wildcard is present, the statement does nothing and execution continues after it.

Indent each case suite under its pattern, just like other Python compound statements. A case body can contain multiple statements, and it can return, raise, or call another function.

3. Combine values and add conditions

Use an OR pattern (|) when different literal values share the same action:

def permission_message(status):
    match status:
        case 401 | 403:
            return "Authentication or permission problem"
        case 200 | 201 | 204:
            return "Request succeeded"
        case _:
            return "Unhandled status"

Patterns answer whether the subject has a particular value or shape. Add a guard—an ordinary Boolean condition after the pattern—when more filtering is needed:

def classify(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"

A pattern must match before its guard is evaluated. If the guard is false, Python proceeds to the next case. Case order therefore matters when a broad pattern or guarded case could overlap with a later one.

4. Match structured data and extract values

Structural matching is useful when the branch depends on both the type or shape of input and the values inside it. For example, a small command parser can match token lists and bind selected parts:

Sequence patterns can check a command’s shape and bind its parts for use in the matching branch.
Sequence patterns can check a command’s shape and bind its parts for use in the matching branch.
def handle_command(line):
    match line.split():
        case ["quit"]:
            return "Goodbye"
        case ["go", direction]:
            return f"Moving {direction}"
        case ["get", item]:
            return f"Taking {item}"
        case ["say", *words] if words:
            return " ".join(words)
        case _:
            return "Unrecognized command"

for line in ("go north", "get key", "say hello there", "quit", "go"):
    print(handle_command(line))

["go", direction] requires a two-item sequence whose first item equals "go"; the second item is bound to direction. The starred name in ["say", *words] collects any remaining items into a list. The guard rejects the empty remainder.

Mapping patterns check for specified keys and bind their values. Extra keys are allowed unless your code checks them separately:

def route_event(event):
    match event:
        case {"type": "created", "id": item_id}:
            return f"Created {item_id}"
        case {"type": "deleted", "id": item_id}:
            return f"Deleted {item_id}"
        case {"type": event_type}:
            return f"Other event: {event_type}"
        case _:
            return "Malformed event"

print(route_event({"type": "created", "id": 42, "source": "api"}))

You can also use class patterns to match an instance and selected attributes. For example, given a dataclass Point, case Point(x=0, y=y_value): can identify points on the vertical axis and bind the y-coordinate. The class must support the relevant pattern-matching behavior; dataclasses support positional matching by default, and named attributes can be matched explicitly.

See the official Python 3.10 tutorial for examples, and PEP 634 for the formal specification.

5. Avoid common pattern mistakes

A bare name captures; it does not compare

This is a frequent source of bugs:

match color:
    case RED:
        print("red")

RED is a capture pattern. It binds the subject to a new name and matches anything, so later cases may never run. To compare a named constant, use a qualified name such as case Colors.RED:, where Colors is a class or module namespace. Or use a literal pattern like case "red":.

Do not expect fall-through

Python does not run the next case body after a match. Combine values with | if they should share a body. If two branches need different work, keep separate cases.

Use ordering deliberately

A wildcard matches everything, so place case _: last. More generally, put specific patterns before broad patterns. An irrefutable pattern such as a bare capture or wildcard can make following cases unreachable. Guards can narrow a pattern, but a false guard allows matching to continue.

Do not depend on failed-match bindings

If part of a pattern binds a name but a later part fails, do not rely on whether that name was set or what value it has. The language reference says bindings from a failed partial match are not a stable basis for program logic. Keep work that uses extracted names inside the case suite that successfully matched.

6. Choose match, if/elif, or a dictionary

Situation Good fit Reason
A few arbitrary conditions or ranges if/elif Direct for inequalities, compound expressions, and range checks.
Exact choices or grouped values on Python 3.10+ match/case Readable alternatives, an optional wildcard, and guards.
Input structure determines the action match/case Can test sequence, mapping, and class shapes while binding fields.
Python 3.9 or older if/elif or dictionary These alternatives parse on older interpreters.
Simple key-to-value or key-to-function lookup Dictionary Compact dispatch table when each key maps directly to an outcome.

Here is an if/elif version for older Python or conditions that are not simple patterns:

def http_error_legacy(status):
    if status == 400:
        return "Bad request"
    elif status in (401, 403):
        return "Authentication or permission problem"
    elif status == 404:
        return "Not found"
    else:
        return "Other status"

A dictionary works well for direct dispatch. Use .get() to specify a default without raising KeyError:

def ok():
    return "OK"

def not_found():
    return "Not found"

def other():
    return "Other status"

handlers = {200: ok, 404: not_found}

def dispatch(status):
    return handlers.get(status, other)()

print(dispatch(404))

Be careful with a dictionary of computed results: Python evaluates its values when it builds the dictionary. Store functions when only one branch’s work should execute. A dictionary also uses hashable keys and exact key lookup; it is not a replacement for range tests or structural matching.

7. Troubleshooting

Symptom Likely cause Fix
SyntaxError at match The active interpreter is Python 3.9 or older, or the code is being run by a different environment than expected. Check python --version and the interpreter configured in the IDE, virtual environment, container, or deployment. Upgrade or replace the syntax with if/elif or dictionary dispatch.
Every input takes the same branch A bare name such as case status: captures any value. Use a literal or qualified constant for equality, and keep capture names for values you intend to bind.
A later case never runs An earlier broad or irrefutable pattern already matches, or an earlier case has the same literal. Move specific cases above broad ones; remove accidental captures; use guards when conditions need narrowing.
A case runs but a value is missing The pattern shape does not bind the field you expect, or the case did not match the assumed input structure. Inspect the input type and shape; make the expected sequence length or mapping keys explicit, and handle malformed input with a fallback.
Dictionary dispatch raises KeyError Code uses handlers[key] for a key that is absent. Use handlers.get(key, default) or explicitly test membership.

8. Performance, reliability, and maintenance

Choose the form that makes the conditions easiest to inspect and update. The language specification defines match behavior, not a speed guarantee. Do not assume it is faster than if/elif or a dictionary; if branching is a measured bottleneck, benchmark representative inputs in the application’s actual Python version.

For reliability, make invalid and unexpected inputs intentional. Add a fallback that returns a safe result, raises a clear exception, or records an unhandled event when silently ignoring it would hide a defect. Validate external data before assuming it has a particular structure. Keep pattern suites short and move substantial work into named functions so each branch is easy to review.

For maintainability, group alternatives only when they truly share behavior, order narrow patterns before broad ones, and add tests for boundary and malformed inputs. If a dictionary dispatch is easier to extend, use it; if a branch depends on shape and must extract data, pattern matching may be clearer.

Or skip the browser setup

This Python guide includes browser screenshots as a concrete example of work developers may automate. If your task is to capture a web page rather than learn browser automation, ScreenshotNeo offers a one-request screenshot API; its API docs describe the request options.

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)

It removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. See the docs for setup and options, then sign up free for 1,000 screenshots a month, no card required.

FAQ

Does Python have switch-case?

It has switch-style branching through match/case in Python 3.10 and later. The feature is structural pattern matching, which does more than compare a value against constants.

What is the default case in Python?

Use case _: as the wildcard fallback. Without a matching case or wildcard, execution simply continues after the match statement.

Does match/case fall through?

No. Only the first matching case body executes. Use an OR pattern such as case 401 | 403: to share a body.

Can I use match/case on Python 3.9?

No. Python 3.9 cannot parse the syntax. Use if/elif or dictionary dispatch, or set Python 3.10 as the minimum runtime.

Sources