Type Hints and Static Typing
Annotate your code so a checker can find whole classes of bugs before you ever run it
mypy reads those hints and flags mismatches without running your program. You keep Python's flexibility and gain a compiler-style safety net that scales to large codebases.
- write function and variable annotations with the modern 3.11+ syntax (
list[int],X | None), - reach for the right tool from the
typingmodule (Optional,Callable,Literal,TypedDict,Protocol...), - run
mypyand read its errors, - write generic code with
TypeVarand structural interfaces withProtocol, - explain the honest trade-offs of gradual typing and where hints stop helping.
1. A hint is metadata, not a runtime check
The single most important fact about type hints: CPython does not enforce them. An annotation is stored as metadata and otherwise ignored while your program runs. Nothing raises just because the actual value disagrees with the hint.
def double(n: int) -> int:
return n * 2
print(double("ab")) # runs fine: prints abab
print(double.__annotations__)
# {'n': <class 'int'>, 'return': <class 'int'>}
Passing a str where the hint says int does not raise; "ab" * 2 is valid Python, so it prints abab. The hint is advisory. It becomes valuable only when a static checker reads it before you run the code and says: "line 4 passes str to a parameter typed int". This split — annotations at author time, checking as a separate step, plain dynamic behaviour at runtime — is the whole model. Annotations live in __annotations__ and are consumed by tools (mypy, pyright, IDEs, pydantic, dataclasses) rather than by the interpreter itself.