typing.override: Validate Method Overrides

Published on: September 5, 2026
Reading time: 6 minutes
Software developer reviewing Python code and overridden methods

The typing.override decorator makes it explicit that a method in a subclass is intended to replace a method defined by a parent class. Added to Python’s standard library in Python 3.12, it does not change normal runtime dispatch. Its value comes from static analysis, clearer code reviews, and safer maintenance of object-oriented hierarchies.

In a small program, overriding a method may look obvious. In a large codebase, a renamed base method, a typo, or an incompatible signature can silently turn an override into an unrelated new method. The program may still run, but polymorphic behavior can be lost. By marking the method with @override, you give type checkers and IDEs a concrete intention they can verify.

Basic example

from typing import override

class Notifier:
    def send(self, message: str) -> None:
        print(message)

class EmailNotifier(Notifier):
    @override
    def send(self, message: str) -> None:
        print(f"Email: {message}")

The decorator tells the analyzer that send must exist in an ancestor. If the subclass accidentally declares sned, the checker can report that no matching base member exists. Without the decorator, the typo is merely a valid new method and may remain unnoticed until runtime behavior is tested.

Why typing.override matters

The first benefit is executable documentation. A reader immediately knows that the method participates in an inherited contract. There is less need to inspect every parent class to understand why the method exists.

The second benefit is refactoring safety. Suppose Notifier.send is renamed to notify. Every subclass method marked with @override can become an actionable diagnostic. The affected implementations are discovered by the analyzer instead of by production failures.

The third benefit is signature checking. Static tools can compare parameter types, return types, optional arguments, and overload relationships. An override that violates substitutability is reported before the code is deployed.

Compatible method signatures

An overriding method should respect the contract of the base class. A subclass must remain usable wherever the parent type is expected. Narrowing an accepted parameter is usually unsafe.

from typing import override

class Repository:
    def save(self, value: object) -> bool:
        return True

class TextRepository(Repository):
    @override
    def save(self, value: str) -> bool:
        return bool(value)

The base class accepts any object, while the subclass accepts only strings. Code typed as Repository is allowed to call save(123); the subclass would fail that promise. A type checker can therefore reject the override.

Return values are often allowed to become more specific. If a base method returns Animal, an overriding method may return Dog when Dog is a subtype of Animal. This is a covariant return and preserves the caller’s expectations.

Abstract base classes

@override works especially well with abc.ABC and @abstractmethod. The abstract method defines the required operation; the decorator records that the concrete implementation intentionally fulfills it.

from abc import ABC, abstractmethod
from typing import override

class Parser(ABC):
    @abstractmethod
    def parse(self, text: str) -> int:
        raise NotImplementedError

class DecimalParser(Parser):
    @override
    def parse(self, text: str) -> int:
        return int(text, 10)

This combination produces a readable architecture. The base class owns the contract, and each implementation clearly identifies the member it provides.

Properties and descriptor decorators

Overrides are not limited to ordinary instance methods. Properties, class methods, and static methods may also replace inherited members. Decorator order matters because each decorator receives the result of the decorator below it.

from typing import override

class Document:
    @property
    def format(self) -> str:
        return "generic"

class PDF(Document):
    @property
    @override
    def format(self) -> str:
        return "pdf"

Placing @override close to the underlying function usually gives analyzers the clearest information. Tool support can differ around complex descriptors, so keep your checker and IDE current and follow the conventions documented by the chosen tool.

Multiple inheritance and super

In multiple inheritance, @override states that a matching member exists somewhere in the ancestry. It does not choose which implementation will run. Python’s method resolution order, or MRO, still controls the target reached through super().

class Logged:
    def run(self) -> None:
        print("log")

class Audited:
    def run(self) -> None:
        print("audit")

class Service(Logged, Audited):
    @override
    def run(self) -> None:
        super().run()
        print("service")

Here, super().run() follows Service.__mro__. The decorator checks the existence and compatibility of an inherited member, but it does not alter dispatch.

Protocols and structural typing

A protocol can be satisfied structurally without inheritance. A class may have all required methods and still not derive from the protocol. In that situation, @override may not be appropriate because there is no actual ancestor member being replaced.

Use protocols to express capability-based compatibility. Use @override when a subclass explicitly participates in a nominal inheritance hierarchy. A class can combine both techniques, but the distinction helps avoid misleading annotations.

Supporting older Python versions

Projects that support Python 3.11 or earlier can import the backport from typing_extensions.

try:
    from typing import override
except ImportError:
    from typing_extensions import override

Libraries often prefer importing consistently from typing_extensions until they drop older interpreters. Applications with a strict Python 3.12 minimum can import directly from typing.

Runtime behavior

The decorator is primarily a typing marker. It does not prevent the program from running when an override is invalid, and it does not enforce a parent call. Some implementations may set a runtime attribute such as __override__ when possible, but application correctness should not depend on that detail.

Static checking must be part of the development workflow. Run a checker in continuous integration and configure the IDE to display diagnostics locally. Without a checker, the annotation remains useful to readers but loses most of its protective value.

Tests are still required

@override checks structural consistency, not business logic. A method can have a perfectly compatible signature and still calculate the wrong result, skip an important side effect, or call super() at the wrong time. Unit and integration tests remain necessary.

Tests should cover behavior through the base interface, not only through the concrete subclass. This verifies that polymorphism works as intended and that every implementation obeys the same observable contract.

Team adoption

Introduce the decorator first in framework extension points, repositories, adapters, plugin systems, serializers, and service classes with many implementations. These areas benefit most from explicit override contracts.

Some analyzers can require an override marker whenever a method replaces a base member. Enabling that rule provides consistency and prevents developers from marking only a few selected methods. Apply the rule gradually to avoid overwhelming an existing codebase with unrelated changes.

Common mistakes

Do not use @override merely because two methods share a name. There must be an inheritance relationship. Do not assume that the decorator fixes incompatible parameter types. It only makes the problem easier to detect. Do not use deep inheritance hierarchies simply to take advantage of the annotation; composition may still produce a simpler design.

Also review whether a call to super() is required. Some overrides replace behavior completely, while cooperative multiple inheritance relies on every implementation forwarding the call. The decorator cannot decide which model is correct.

Continue with our guides to Python inspect, dataclass_transform, TypeVarTuple, and StrEnum. The official typing.override documentation and PEP 698 provide the authoritative specification and motivation.

Conclusion

typing.override is a small annotation with a strong maintenance payoff. It makes inherited intent visible, catches misspelled or removed base members, improves signature validation, and turns risky refactors into clear diagnostics. Used together with static analysis and tests, it makes Python class hierarchies easier to understand and safer to evolve.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Software developer working with Python queues and threads
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    queue.SimpleQueue: Thread-Safe FIFO Queue

    Learn Python queue.SimpleQueue for thread-safe FIFO queues, worker pipelines, clean shutdown, and reliable producer-consumer designs.

    Ler mais

    Tempo de leitura: 5 minutos
    04/09/2026
    Software developer working with Python enums and API code
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python StrEnum: String Enums

    Learn Python StrEnum for string-based enums, input validation, JSON serialization, APIs, configuration, and cleaner domain contracts.

    Ler mais

    Tempo de leitura: 4 minutos
    04/09/2026
    Folders and directories for Python contextlib.chdir
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    contextlib.chdir: Restore Directories Automatically

    Learn Python contextlib.chdir to change directories temporarily, restore paths safely, isolate tests, and avoid global-state bugs.

    Ler mais

    Tempo de leitura: 5 minutos
    03/09/2026
    Python code execution and performance monitoring
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    sys.monitoring: Low-Overhead Instrumentation

    Learn Python sys.monitoring for low-overhead instrumentation with selective events, callbacks, tooling, and safe observability.

    Ler mais

    Tempo de leitura: 5 minutos
    03/09/2026
    Software developer organizing object data with Python operator.attrgetter
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    operator.attrgetter: Sort Objects by Attributes

    Learn Python operator.attrgetter to sort, group, and transform objects by simple or nested attributes with clearer reusable code.

    Ler mais

    Tempo de leitura: 4 minutos
    02/09/2026
    Asynchronous programming with Python asyncio.Runner
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    asyncio.Runner: Reuse the Event Loop Safely

    Learn Python asyncio.Runner to reuse an event loop, control context, signals, debug mode, cancellation, and safe asynchronous shutdown.

    Ler mais

    Tempo de leitura: 6 minutos
    02/09/2026