Why are match and case usable as ordinary variable names in Python?
answer
- Upgrading to 3.10 broke nobody's variables
- Reserved only where the grammar expects it
- keyword.iskeyword answers False here
- A second keyword list exists in the stdlib
basics
~20 sThey are soft keywords: reserved only in the grammatical positions where a match statement can appear. Everywhere else the parser treats them as ordinary identifiers, so upgrading to Python 3.10 broke no code that already used those names.
solid answer
~50 sPython 3.10 added `match` and `case` as **soft keywords**, meaning the grammar recognises them only in context — as the head of a `match` statement and of its `case` clauses. Anywhere else they are plain identifiers, so `match = 1`, a parameter named `case`, and the `match` method on a compiled regular-expression object all keep working. This was possible because the PEG parser adopted in 3.9 by PEP 617 can decide by lookahead rather than by tokenising a fixed reserved-word list. You can check it at runtime: `keyword.iskeyword("match")` is `False`, `keyword.issoftkeyword("match")` is `True`, and `keyword.softkwlist` holds `_`, `case`, `match` and — since 3.12 — `type`. The motivation was purely compatibility: `match` is one of the most common names in Python code, and hard-reserving it would have broken a large amount of working code on upgrade.
code
python · 8 linesimport keyword
match = {"a": 1}
case = "value"
print(keyword.iskeyword("match"), keyword.issoftkeyword("match"))
print(sorted(keyword.softkwlist))
print(match["a"], case)go deeper
Remember the practical fact: you may still name a variable or a method match or case, and existing code using those names kept working when Python 3.10 shipped. The word is special only at the start of a match statement.
Explain the mechanism — a soft keyword is reserved by position, not by the tokeniser — and show you can prove it with keyword.issoftkeyword and keyword.softkwlist rather than keyword.iskeyword.
Add the engineering context: the PEG parser from 3.9 made contextual keywords viable, and the choice was driven by not breaking a huge body of code that already used the name. Note where tooling that reads only the hard keyword list gets it wrong.
Own the language-evolution view: soft keywords are now the standard route for adding statements without a compatibility break, as PEP 695 reused in 3.12, and that shapes how you assess the upgrade risk of new syntax across a large codebase.
## Hard keywords versus soft keywords A hard keyword — `if`, `class`, `return`, `lambda` — is reserved everywhere. The tokeniser recognises it before the grammar sees the line, so it can never be a name, an attribute, or a parameter. Adding one to the language is a breaking change: every program that used it as an identifier stops compiling. A soft keyword is reserved only where the grammar expects it. Outside those positions it is an ordinary identifier. Python has four of them as of 3.14: `_`, `case`, `match` and `type`. `keyword.kwlist` lists the hard keywords; `keyword.softkwlist` lists the soft ones, and `keyword.iskeyword` and `keyword.issoftkeyword` answer the two questions separately — `keyword.iskeyword("match")` returns `False`. ## Why match had to be soft When PEP 634 proposed pattern matching for 3.10, the name problem was immediate. `match` is everywhere in real Python: the result of a regular-expression search is conventionally called `match`, compiled regular-expression objects have a `match` method, and functions named `match` are common in parsers, routers and test helpers. `case` is nearly as common in test-suite code. Hard-reserving either word would have broken a large fraction of the ecosystem on the day 3.10 shipped, for no benefit to anyone. The soft-keyword route made the feature additive: code that never writes a `match` statement is unaffected by its existence. ## What made it practical Until Python 3.9, CPython used an LL(1) parser, which cannot easily look far enough ahead to decide whether a line beginning with `match` is a statement header or an expression. PEP 617 replaced it with a PEG parser in 3.9, which backtracks and can try alternatives. That is what allows the grammar to say, in effect: this is a match statement if the name `match` is followed by an expression, a colon, and an indented block of `case` clauses; otherwise the name is just a name. The decision is purely syntactic. It does not consult what `match` is bound to at runtime — bindings do not exist at parse time — so a module can bind a variable named `match`, define a function named `case`, and still contain a working match statement further down. It is legal and it is confusing; most style guides suggest renaming the variable rather than exercising the parser's cleverness. ## Where the ambiguity shows A few lines are genuinely interesting to the parser. `match(x)` with no trailing colon is a call. `match (x):` — a name, a parenthesised expression and a colon, followed by an indented `case` — is a match statement whose subject happens to be parenthesised. `match = case` is two ordinary names. The rule to hold in your head is that the colon plus the following `case` block is what commits the line to being a match statement. ## Practical consequences Tooling is the main place this leaks. Syntax highlighters, older linters and naive source-scanning scripts sometimes colour or flag `match` as a keyword everywhere, or the opposite. Anything that decides keyword-ness from `keyword.kwlist` alone will be wrong about soft keywords in both directions, which is exactly why `keyword.softkwlist` and `keyword.issoftkeyword` exist. The pattern has since been reused: Python 3.12's PEP 695 added the type-alias statement `type Alias = int`, and `type` is a soft keyword for exactly the same reason — the builtin `type` is far too common a name to reserve. Expect future syntax to keep taking this route, because it is now the accepted way to add a statement without a compatibility break. ## A note on the underscore `_` is on the soft-keyword list too, because a match statement gives it the special meaning of a wildcard case. Outside that position it stays what it has always been: an ordinary name, conventionally used for a value you intend to discard, and bound by the interactive interpreter to the last result. Nothing about pattern matching changed either of those uses. ## Answering it well Name the concept ("soft keyword"), give the reason (compatibility — `match` was already a very common identifier), give the enabling machinery (the PEG parser from 3.9, PEP 617), and give the runtime check (`keyword.issoftkeyword` and `keyword.softkwlist`). Adding that `type` joined the list in 3.12 shows you have watched the language keep using the technique.
- How do you check at runtime whether a given name is a soft keyword?`keyword.issoftkeyword(name)` returns True for soft keywords, and `keyword.softkwlist` lists them all — on Python 3.14 that is `_`, `case`, `match` and `type`. `keyword.iskeyword` and `keyword.kwlist` cover only the hard, always-reserved keywords, so any tool that consults just those will misjudge soft keywords.
- Which soft keyword was added after match and case, and why was it also made soft?`type`, added in Python 3.12 by PEP 695 for type-alias statements such as `type Alias = int`. Making it soft was mandatory for the same reason: `type` is a builtin and an extremely common parameter and variable name, so hard-reserving it would have broken an enormous amount of working code.
- Does binding a variable named match prevent a match statement later in the same scope?No. Whether a line starts a match statement is decided by the grammar — a name, an expression, a colon and an indented block of `case` clauses — and parsing happens long before any binding exists. The two coexist in one module, though it is confusing enough that renaming the variable is the better call.
A hard keyword is a word banned from the whole building; a soft keyword is banned only in one room, and outside that room you may use it as freely as ever.
saying these in an interview costs you the question
- Says match became a reserved word in Python 3.10
- Expects keyword.kwlist to contain match and case
- Thinks assigning to a name called match is a SyntaxError
- Believes the parser inspects runtime bindings to decide
- Confuses soft keywords with a __future__ import