Why does `case [x, y]` in a Python match statement never match the string "ab"?
answer
- Three built-in types are special-cased out
- It is a sequence everywhere else
- Think what a two-letter word would bind
- The exclusion is written into PEP 634
basics
~20 sPattern matching deliberately excludes str, bytes and bytearray from sequence patterns, even though they are sequences everywhere else in Python. Silently destructuring text into characters is almost always a bug, so the pattern just fails and the next case is tried.
solid answer
~40 sA sequence pattern begins with a type test, and `str`, `bytes` and `bytearray` are special-cased out of it by PEP 634 - they never match `case [x, y]`, `case [*chars]` or any other sequence pattern. This is not an oversight: `issubclass(str, collections.abc.Sequence)` is still `True`, and text still iterates into characters everywhere else. The exclusion exists because treating a string as a sequence of characters in a pattern is essentially never what the author meant - the classic bug is passing one recipient string where a list of recipients was expected and having it silently destructure. Failing the pattern turns that silent misbehaviour into a fall-through you can catch with an explicit case. Match text with a literal pattern, or test for the type before you reach the sequence patterns.
code
python · 11 linesdef label(value):
match value:
case [first, *_]:
return f"sequence starting {first!r}"
case _:
return f"no sequence match for {value!r}"
print(label(["[email protected]"]))
print(label("[email protected]"))
print(label(b"ops"))
print(label(("a", "b")))go deeper
Remember the fact and the shape of the bug: str and bytes never match a bracketed pattern, so a value you thought was a list of strings but is one string quietly falls through to the catch-all case.
Explain the rationale as well as the rule - character-by-character destructuring is never the intent, and unbounded recursion into one-character strings is the other hazard - and note that the ordinary issubclass answer still says sequence.
Show how you would make the fall-through diagnosable in a running service: include the subject's type in the catch-all's message, or normalise a lone string into a one-item list at the input boundary so the ladder never sees the ambiguous shape.
Own the wider position on defensive language design: where else your codebase should refuse a plausible-but-wrong shape rather than accept it, and whether validation belongs at the boundary instead of in every dispatch site.
Python's data model happily calls a string a sequence: `str` is registered with `collections.abc.Sequence`, `issubclass(str, collections.abc.Sequence)` returns `True`, and `len`, indexing, slicing and iteration all behave as you would expect. Structural pattern matching then goes out of its way to contradict that. A sequence pattern - the bracketed form in a `case` - refuses `str`, `bytes` and `bytearray` outright. `case [x, y]` does not match `"ab"`; `case [*rest]`, which matches every other sequence including an empty one, does not match `""` either. ## Why the special case exists The motivating bug is one every codebase has shipped. Somewhere a value is meant to be a collection of strings - recipients, field names, paths - and one day it arrives as a single string instead. - Without the exclusion, `case [first, *rest]` would match that string, bind `first` to its first character and carry on as if a one-element collection had arrived with a very short first element. Nothing raises; the wrong branch simply runs. - The same shape shows up in **recursive patterns**: a pattern designed to walk nested sequences would descend into a string, then into its characters, and since a one-character string is itself a one-character sequence, the recursion has no natural floor. PEP 634's authors chose to make the pattern fail rather than let either of these compile into silence. ## What the rule actually is The type test on a sequence pattern is a **flag check** on the subject's type, and `str`, `bytes` and `bytearray` do not carry it, however sequence-like they are in every other respect. Nothing about the pattern's contents changes this: arity, star, nesting and guards are all irrelevant, because the type test comes first and the pattern has already failed. Two consequences follow: 1. A `case _` or a bare capture still matches text, because those are not sequence patterns. 2. And a string nested *inside* a matched sequence is untouched by the rule: matching `["[email protected]"]` against `case [addr]` binds the whole string to `addr`, because `addr` is a capture pattern, not a sequence pattern. ## How to match text on purpose Since the failure is deliberate, you say what you mean instead. - A **literal pattern** such as `case "digest":` matches an exact string with `==`. - A **type test** before the sequence cases separates the string arm from the collection arm - and because a `case` ladder stops at the first match, putting the text case first makes the intent obvious to a reader. - In practice the tidiest fix is often **upstream**: normalise a lone string into a one-element list at the boundary, so the rest of the function only ever sees collections and the ladder has one shape fewer to handle. ## The mirror-image trap Note which direction the surprise runs. Newcomers assume the danger is that a string *will* be destructured - it is the opposite: the danger is a value you believed was a list arrives as a string, silently matches nothing, and falls through to your catch-all where it is reported as malformed input. That is a much better failure than a wrong branch, but it is still a failure you have to recognise, and the diagnosis is not obvious from the message your catch-all prints. Include the subject's type in that message and the whole class of bug becomes a five-second read. ## Related exclusions worth holding together - `dict` and `set` never match a sequence pattern either, but for a different reason - they are not sequences at all; a `dict` matches a mapping pattern instead. - Iterators and generator objects also fail, because measuring one would mean consuming it. So the honest summary of a sequence pattern's type test is: **the ordered, indexable, re-readable containers, minus the three text types that Python protects you from.** Say that sentence in an interview and you have covered the whole rule, its rationale and its two neighbouring cases.
- Is `issubclass(str, collections.abc.Sequence)` True or False?`True`. `str` is registered with `collections.abc.Sequence`, so ordinary `isinstance` and `issubclass` checks treat it as a sequence. Pattern matching does not use that check - it consults a separate type flag from which `str`, `bytes` and `bytearray` are excluded. The two answers genuinely disagree, and that disagreement is intentional rather than a bug.
- Does `case [addr]` matching `["[email protected]"]` bind the whole address or its first character?The whole address. The outer brackets form a sequence pattern that matches the one-item list; `addr` inside it is a capture pattern, which matches any object and binds it whole. The exclusion only stops a *string subject* from matching a sequence pattern - it says nothing about strings sitting inside a sequence that does match.
- Do `dict` and `set` subjects match a sequence pattern?No, but for a different reason from `str`: they are not sequences at all. A `dict` matches a mapping pattern instead, and a `set` matches neither sequence nor mapping patterns - only a capture, a wildcard or a class pattern will take it. Iterators and generator objects also fail, since measuring one would consume it.
It is like a mail merge that refuses to accept a bare address where a list of addresses belongs: rather than posting one letter per character, it declines the shape and lets you notice.
saying these in an interview costs you the question
- Says the string matches because it has two characters
- Claims str is not a sequence in Python at all
- Thinks the exclusion applies to strings nested inside a matched sequence
- Believes adding a star or a guard makes text match
- Assumes a set or a generator matches a sequence pattern