In a module using @typing.overload, which def actually runs at call time?
answer
- Only one of them has a body
- The decorated ones are for the checker
- Ellipsis body, never executed
- The last def binds the name
- A stub-only name raises when called
basics
~20 sOnly the final, undecorated def has a real body and runs. The @typing.overload stubs above it exist for the type checker, their ellipsis bodies are never executed, and a name that has only stubs raises NotImplementedError when called.
solid answer
~50 s`@typing.overload` declares one *signature* at a time, not one function at a time. You write two or more decorated stubs with the same name and a body of just `...`, then a single undecorated `def` with that name holding the real code. At runtime each `def` rebinds the module-level name, so the last one — the implementation — is what every caller reaches. The decorator itself just records the stub for the checker and returns a placeholder that raises `NotImplementedError` if it is ever called, which is what you hit if you forget the implementation. The stubs perform no dispatch and hold no code; a type checker reads them and gives each call site the return type of the signature that matched. In a `.pyi` stub file the implementation is omitted, because such files are never executed.
code
python · 10 linesfrom typing import overload
@overload
def first(values: list[int]) -> int: ...
@overload
def first(values: str) -> str: ...
def first(values: list[int] | str) -> int | str:
return values[0]
print(first([2, 4]), first("2.4"))go deeper
Be ready to read a file with several same-named defs and say which one runs: the final, undecorated one. Know that the ellipsis bodies are placeholders for the type checker, not abstract methods.
Explain the mechanics out loud: each def rebinds the name, the decorator stores the stub and returns a placeholder, and nothing consults the stubs at call time. Mention the .pyi case where no implementation exists at all.
Show that you treat the stubs as a checker-only promise: the implementation still needs its own branching and validation, because unchecked callers reach it anyway. Be able to say what a stub-only name does when called.
Own the convention for the codebase — which public APIs are worth the duplication, whether overloads live inline or in stub files, and how you stop stubs from drifting away from the implementation they describe.
## One name, several declared signatures `typing.overload` is a declaration device. It lets you tell a type checker that a single callable has more than one legal shape — different parameter types, different arities, different return types — while Python itself still sees exactly one function object bound to the name. The shape is fixed and mechanical: two or more decorated stubs, each with the same name and a body that is only `...`, followed by exactly one undecorated `def` with the same name that carries the working code. ```python from typing import overload @overload def first(values: list[int]) -> int: ... @overload def first(values: str) -> str: ... def first(values: list[int] | str) -> int | str: return values[0] ``` ## What the interpreter does with that Nothing special, which is the point. A `def` statement builds a function object and binds it to a name in the enclosing namespace; three `def first` statements in a row simply rebind `first` three times. The last binding wins, and the last binding is the implementation, so every call runs the code you wrote. The decorator's own runtime behaviour is deliberately small. It records the stub in a registry keyed by module and qualified name, then returns a placeholder function whose only job is to raise `NotImplementedError` if anybody calls it. That placeholder is what the name holds *between* the stubs, and it is what the name is left holding if the implementation is missing. So a module of stubs with no implementation imports cleanly and fails on the first call — precisely the class of bug the checker is meant to catch first. The `...` bodies are `Ellipsis` expressions used as a no-op, the same convention type stub files use. They do not mean abstract, they do not mean "not implemented yet" in any language-level sense, and they carry no meaning to the interpreter beyond "this body evaluates a constant". Putting working code in a stub body is pointless: nothing will ever call it. ## What the checker does with it A type checker collects the stubs in source order and, for each call site, picks the first stub whose parameters accept the arguments; the call site then gets that stub's return type rather than a union. The implementation's signature is checked separately — it must be broad enough to accept every stub's arguments and its return must be compatible with each stub's — but callers never see it. That division is the whole idea and also the trap. The stubs are a promise to the checker. They dispatch nothing and validate nothing, so code the checker never saw — an untyped caller, an argument that arrived as `Any` from parsed configuration, a call assembled from `**kwargs` — reaches the implementation regardless of whether any stub would have allowed it. Runtime enforcement is the implementation's job, not the annotations'. ## Runtime introspection Since Python 3.11 the registry is public. `typing.get_overloads(func)` returns the stub function objects for a function in declaration order, and `typing.clear_overloads()` empties the registry, which matters for tools that generate and re-import code in a long-lived process. Before 3.11 the stubs were unrecoverable once the module finished importing. Under Python 3.14, annotations are evaluated lazily (PEP 649), so reading `__annotations__` on one of those returned stubs is what triggers evaluation of its annotation expressions. ## Where the implementation is absent on purpose In a `.pyi` stub file — a type-only file that is never imported at runtime — you write the overload stubs and stop. There is no implementation because there is no execution, and a checker will not complain. That is the original home of the feature: describing the many shapes of a function whose real body lives in C, or in code you do not own. Overloads work the same way on methods as on module-level functions: the stubs sit in the class body, in stub-then-implementation order, and the last definition is the one that ends up in the class namespace. ## How to say it in an interview "Overloads are a compile-time story. Three defs, one function: the decorated ones are declarations for the checker with empty bodies, the undecorated last one is the function that actually exists. If you call a name that only has stubs, you get `NotImplementedError`, because that is all the decorator leaves behind."
- What happens if a module declares @typing.overload stubs but never defines the implementation?A type checker reports the missing implementation, and at runtime the name stays bound to the placeholder the decorator returned, so the first call raises `NotImplementedError`. The one place that is correct is a `.pyi` stub file: it is never imported at runtime, so the stubs are the whole declaration and no implementation is expected.
- Can you get at a function's overload stubs at runtime?Yes, since Python 3.11: `typing.get_overloads(func)` returns the registered stub functions in declaration order, and `typing.clear_overloads()` empties the registry. It is introspection only — the objects returned are the placeholders, so calling one raises `NotImplementedError`; you read their annotations, you do not invoke them.
The stubs are the menu printed for the customer; the implementation is the single kitchen behind it. Reading the menu never cooks anything.
saying these in an interview costs you the question
- Says Python picks the matching def at call time
- Thinks the ellipsis body makes the stub abstract
- Puts working code inside an overload stub body
- Expects the implementation def to be decorated too
- Assumes a disallowed argument mix raises at runtime