What does the @override decorator from Python's typing module tell a static type checker?
answer
- A safety net for renames
- Declares intent, changes no behaviour
- The checker complains, never the interpreter
- Opposite job to the final decorator
- PEP 698, shipped in 3.12
basics
~20 styping.override, added in Python 3.12 by PEP 698, marks a method as deliberately replacing one inherited from a base class. A checker reports an error when no base class declares that name. At run time the decorator does nothing.
solid answer
~50 s`typing.override` (PEP 698, Python 3.12) marks a method as deliberately overriding one inherited from a base class, and a static checker then verifies that some base class really declares that name. That closes a gap Python cannot close by itself: renaming or deleting a base method leaves the subclass method silently orphaned — it still exists and still runs if called directly, but calls made through the base's name now reach the base implementation, and nothing anywhere complains. At run time the decorator is inert: it returns the same function object, records a marker attribute so runtime tooling can see the intent, and never raises. It does not make the method final, does not require the base method to be abstract, and does not by itself validate the signature — the usual compatibility rules for an override apply whether or not you write it.
code
python · 13 linesfrom typing import override
class Geocoder:
def lookup_address(self, address: str) -> str:
return address.title()
class CachingGeocoder(Geocoder):
@override
def lookup(self, address: str) -> str:
return address.upper()
# Runs fine; a checker flags the decorated method as overriding nothing.
print(CachingGeocoder().lookup_address("main st"))go deeper
Be ready to say in one breath what it marks and who reads it: the method must override something, and a type checker is the only thing that ever complains. Remember it is inert at run time and arrived in 3.12.
Explain the mechanics: name-based overriding means a base rename silently orphans a subclass method, the decorator makes a checker verify the name exists on some base, and the decorator itself just hands the function back with a marker attribute.
Show you know it is opt-in and therefore worthless half-applied. Talk about enabling the checker option that requires the marker on every override, and about the failure mode it removes — dead specialisations that no test exercises.
Own the question of what belongs in the checker versus the run time. Argue when a codebase should pay for full override marking, when abstract base classes give the same guarantee more cheaply, and how strictness settings get enforced consistently across teams.
## The failure it exists to catch Overriding in Python is name-based and resolved at run time. A subclass method overrides a base method only because the two happen to share a name; there is no declaration linking them. So if the base method is renamed, or moved, or misspelled in the subclass in the first place, the subclass method does not become an error — it becomes **a new method that nothing calls**. The base implementation quietly takes over for every call made through the base's name, and the subclass keeps a chunk of dead code that looks live. This is a nasty class of bug because it is invisible to everything cheap. The module still imports. The class still instantiates. A large regression pack — say 340 cases — only catches it if some case actually drives the renamed behaviour through that particular subclass, and a subclass that exists to specialise one rare path usually has no such case. The failure surfaces later as "the caching layer stopped caching" or "the specialised formatting is gone", far from the rename that caused it. ## What PEP 698 added Python 3.12 added `typing.override` (PEP 698). You apply it to a method in a subclass: ```python from typing import override class CachingGeocoder(Geocoder): @override def lookup(self, address: str) -> str: ... ``` The decorator is a **declaration of intent** aimed at static analysis. A type checker looks at the decorated method and asks whether any base class of the enclosing class declares a member with that name. If none does, it reports an error at the decorated definition — exactly where the reader can fix it. The check is purely about the name existing on a base; the checker's normal compatibility rules for the signature are applied to every override anyway, decorated or not. The direction of the guarantee matters. `@override` says "this must be overriding something". It does **not** say "nothing may override this" — that is `typing.final`, a different decorator solving the opposite problem. And it does not say "the base must be abstract": an abstract base already gives you a different error (instantiating a class that has not implemented everything abstract), which is why abstract hierarchies feel safer and concrete ones need `@override` most. ## Run-time behaviour At run time the decorator is close to a no-op. It returns the same object it was given, and best-effort sets a marker attribute on it so that runtime introspection tools can tell the method was declared as an override; if the object refuses attribute assignment — some descriptors and C-level callables do — it swallows the failure and returns the object anyway. It never inspects the class, because when the decorator runs the class body has not finished executing and the base classes are not its business. Nothing raises. A method decorated with it that overrides nothing behaves at run time exactly as if the decorator were absent. That is the standard trade in Python's typing story: the standard library ships the vocabulary, the checker supplies the enforcement, and the interpreter stays out of it. If you never run a checker, `@override` buys you documentation and nothing more. ## Using it in practice It is opt-in per method, which means an unmarked override is not itself an error — a hierarchy where only half the overrides carry the marker gets only half the protection. Checkers therefore offer a strictness option that flags any method overriding a base member without the marker; turning that on is what converts the feature from decoration into a real invariant. The usual rollout is: enable the option, let the checker enumerate every override in the hierarchy, add the decorator mechanically, and from then on any base-class rename produces a wall of errors at exactly the subclasses that need editing. On Python 3.11 and earlier the name is not in `typing`; it is available only from the typing backport distribution. If your code must run on both, import it from the backport and the semantics are identical, since the whole feature is a checker convention plus an inert decorator. Two boundaries worth keeping straight in an interview. First, `@override` is not Java's `@Override`: Python's has no compile step to enforce it, so its power is exactly the strictness of the checker you run. Second, it is a *name* check, not a *behaviour* check — an override that keeps the name but breaks the base's contract is caught (if at all) by the signature-compatibility rules, not by this decorator.
- Does decorating a method with typing.override stop deeper subclasses from overriding it again?No. `@override` is an assertion about the method's own base, not about its future subclasses. The decorator that forbids further overriding is `typing.final`, and the two are independent: a method can carry both, meaning "this replaces a base member and nothing below may replace it".
- What happens at import time if a method decorated with typing.override matches no base method?Nothing at all. The decorator returns the function object it was given and records a marker attribute best-effort, swallowing the failure for objects that reject attribute assignment. It cannot inspect the base classes, because the enclosing class does not exist yet when the decorator runs. Only a static checker reports the mistake.
- How would you roll this out across a hierarchy that already has hundreds of overrides?Turn on the checker's strictness option that flags any override lacking the marker, let it enumerate them, and add the decorator mechanically — the edit is safe because it changes no run-time behaviour. Do it before the next rename, not after: a 340-case regression pack will not notice an orphaned override unless some case drives that exact subclass.
It is the label on a spare part that says which part it replaces. The part still fits in the box without the label; the label is what lets an inspector notice that the thing it was meant to replace no longer exists.
saying these in an interview costs you the question
- Thinks the decorator enforces overriding at run time
- Says it raises TypeError when nothing is overridden
- Believes it also makes the method final
- Claims it validates the override's signature by itself
- Assumes it has been in typing since Python 3.0
- Thinks it requires the base method to be abstract