Back to Blog
Python

Python Match Case Default: Using the Wildcard Pattern

python match case default: Learn how the default case in Python's match statement works, how to use the wildcard pattern, and avoid common pitfalls when handling unmat...

pattern matchingmatch statementwildcard patterndefault case
Illustration of a Python match statement with a default wildcard case catching unmatched values.

The python match case default pattern is a fallback that executes when no other pattern matches. In a match statement, every possible input value must be accounted for; if no pattern matches, the statement simply does nothing, which often leads to silent bugs. The default case, written as case _, provides a deterministic way to handle unmatched values.

The Role of the Default Case in match Statements

A match statement evaluates an expression and compares it against a sequence of patterns. Each pattern can include literals, class names, mapping keys, or guards. If none of the patterns match, the statement falls through without executing any branch. That behavior is rarely what you want in production code. The default case gives you a deterministic way to handle unmatched values, similar to else in an if statement, but with pattern-specific semantics.

Consider a simple example:

def describe(value): match value: case 0: return "zero" case 1: return "one" case _: return "other"

Here case _ catches any value that is not 0 or 1. The underscore is not a variable name; it is a wildcard pattern that matches anything without binding the value. This distinction matters when you need to use the unmatched value inside the branch.

Basic Syntax and How the Wildcard Pattern Works

The wildcard pattern _ is a special case in Python's structural pattern matching. It matches any object and does not bind a name. You can use it as the last case to provide a default action. Because it matches everything, it must appear after more specific patterns; otherwise, it would shadow them and make the earlier patterns unreachable.

def classify(value): match value: case _: return "default" case 1: return "one" # This will never execute

The order of cases is significant. The first matching pattern wins. If you put case _ first, it always matches, and the rest of the cases become dead code. Python does not raise an error for unreachable patterns, so you must be careful with ordering.

If you need to use the unmatched value, you can bind it with a name instead of using the wildcard. For example:

def log_value(value): match value: case 1: print("one") case other: print(f"unexpected: {other}")

Here other acts as a default case and also binds the value to the variable other. This is often more useful than _ because you can inspect or log the value.

Using the Default Case with Guards and Complex Patterns

The default case can be combined with guards to create conditional fallbacks. A guard is an if expression attached to a pattern. If the pattern matches but the guard is false, the match continues to the next case. This allows you to have a default that only applies under certain conditions.

def handle_request(status): match status: case 200: return "ok" case 404: return "not found" case code if code >= 500: return "server error" case _: return "unknown status"

In this example, the guard if code >= 500 captures any status code 500 or above. If the status is, say, 302, none of the first three patterns match, so the default case _ runs. The default itself can also have a guard, though it is rarely necessary because it already matches everything.

When dealing with structured data, the default case can be used to handle unexpected shapes. For instance, when matching dictionaries:

def extract_name(data): match data: case {"name": str(name)}: return name case {"first": first, "last": last}: return f"{first} {last}" case _: return "unknown"

The wildcard catches any input that is not a dictionary with the expected keys, or has the wrong value types. This makes the function robust against malformed data without raising an exception.

Common Mistakes When Relying on the Default Case

One frequent mistake is using case _ without realizing that it also matches None and other falsy values. If you expect a default only for a specific type, you need to add a type check or use a class pattern. For example:

match value: case int(): return "integer" case str(): return "string" case _: return "other"

Here case _ catches lists, dicts, floats, and None. If you want to handle None separately, you must add a case for it before the wildcard.

Another mistake is assuming that the default case will catch exceptions raised during pattern matching. It will not. If a pattern's guard raises an exception, the match statement propagates that exception; it does not fall through to the default. For example:

match value: case x if x > 10: return "big" case _: return "small"

If value is a string, the guard x > 10 raises a TypeError, and the default case is never reached. The exception propagates out of the match. To avoid this, you should ensure that guards are safe for all possible input types, or use a type pattern before the guard.

Performance and Runtime Behavior of match with Default

The match statement is not a simple if chain under the hood. It uses a more efficient dispatch mechanism for certain pattern types, such as literals and attribute patterns. However, the default case itself has no special performance cost; it is just the last branch in a linear scan. In practice, the performance difference between match and an equivalent if chain is negligible for most applications. The real benefit is readability and maintainability, not speed.

One subtle runtime behavior is that the wildcard _ does not create a variable. If you try to access _ after the match, you will get a NameError unless you defined it elsewhere. This is different from using a named pattern, which leaves the variable in scope after the match. Be aware of this when debugging.

Compatibility and Migration Considerations

The match statement was introduced in Python 3.10. If your codebase targets earlier versions, you cannot use it. For migration, you can replace match with an if chain, but you lose the expressive pattern syntax. When moving to match, pay attention to the default case: an if chain's else is equivalent to case _, but only if the conditions are properly structured.

If you are working with a codebase that uses match, ensure that all possible inputs are handled. A missing default case may not produce an error, but it can silently ignore values. Adding a default case, even if it just raises an exception or logs a warning, makes the code's behavior explicit and helps catch bugs during development.

When a Named Default Is Better Than an Underscore

Choosing between _ and a named variable depends on whether you need the unmatched value. If you only need to signal that a fallback occurred, _ is fine. If you need to log the value, raise a custom error, or perform further processing, use a name. The name also makes the intent clearer to readers: case other or case unexpected communicates that the value is unexpected, whereas _ can be overlooked.

def process(value): match value: case "start": return "begin" case "stop": return "end" case unexpected: raise ValueError(f"Unexpected command: {unexpected}")

This pattern is common in command parsers and state machines. It ensures that unknown input fails loudly instead of being silently ignored. The default case is not just a safety net; it is a tool for enforcing invariants.

python match case default: Wildcard Pattern Explained | RYUSLOG DEV