Python typing.overload Function Ordering Rules

In Python, the @typing.overload decorator allows developers to declare multiple function signatures to describe how return types depend on input argument types. Because static type checkers like Mypy and Pyright evaluate overloaded signatures sequentially from top to bottom, the order of declaration is critical. This guide outlines the essential rules dictating the ordering of @typing.overload definitions, detailing how type checkers resolve signatures and how to prevent shadowed types.

1. Top-to-Bottom Sequential Evaluation

Static type checkers process @typing.overload signatures in the exact order they are declared in the code. When a function call is analyzed, the checker tests the arguments against each signature starting from the first. The first signature that matches the call arguments is selected, and any subsequent signatures are ignored for that call.

Because of this first-match behavior, ordering determines which return type is inferred whenever two or more signatures overlap.

2. Specific Types Before General Types

The primary rule of overload ordering is to define narrower, more specific types before broader, more general types. If a general type is placed first, it will consume calls intended for the narrower type, effectively shadowing it.

Example:

from typing import Literal, overload

# 1. Most specific: Literal match
@overload
def fetch_data(format: Literal["json"]) -> dict: ...

# 2. Less specific: general str
@overload
def fetch_data(format: str) -> str: ...

# Implementation
def fetch_data(format: str) -> dict | str:
    if format == "json":
        return {"status": "ok"}
    return "raw data"

If format: str were placed first, passing the literal "json" would match the general str signature first, returning str instead of dict.

3. Mutually Exclusive (Disjoint) Types

When argument types do not overlap, declaration order does not affect type inference. For example, if one overload accepts int and another accepts str, neither can match the other's inputs.

@overload
def process(value: int) -> int: ...

@overload
def process(value: str) -> str: ...

In this scenario, switching the order has no functional impact on static analysis. However, keeping related overloads grouped consistently or placing the most common use cases higher improves code readability.

4. Default and Optional Arguments

When functions use default parameter values or varying argument counts:

5. Implementation Must Be Last and Undecorated

The actual implementation of the function must appear immediately after all @typing.overload definitions.