Python type hints went from optional to expected in professional Python codebases between 2020 and 2026. PEP 484 introduced hints in Python 3.5; by 3.12, the typing ecosystem matured enough that the major frameworks (FastAPI, Pydantic, SQLAlchemy 2.0) are built around type hints as a first-class feature, not an afterthought.
The mypy vs pyright choice is the first decision. mypy (maintained by the Python community, used by Dropbox, Stripe, Facebook) is the reference implementation. pyright (Microsoft, powers Pylance in VS Code) is faster and stricter. In practice: pyright catches more errors and runs significantly faster on large codebases (5-10x faster than mypy at scale). For new projects in 2026, pyright/Pylance via the `pylance` VS Code extension is the default. basedpyright is a community fork with even stricter defaults.
Pydantic v2 is how most production Python code does runtime type validation. Define a model as a Python class with type annotations, and Pydantic validates inputs, coerces types, and generates JSON schemas automatically. The v2 rewrite (2023) moved the core to Rust — validation is 5-50x faster than v1. FastAPI uses Pydantic internally for request/response validation, so you get type-safe API development without additional tooling.
Typing async code: use `Coroutine[ReturnType, ...]` for coroutines, `AsyncIterator[T]` for async generators, and `Awaitable[T]` for anything that can be awaited. The `asyncio.gather` return type needs proper annotation — `asyncio.gather(*coros)` returns `tuple[T1, T2, ...]` in Python 3.11+ with the `Unpack` TypeVarTuple. Use `from __future__ import annotations` at the top of files with forward references to avoid evaluation order issues.
Generics with TypeVar: `T = TypeVar('T')` for basic generics. Python 3.12 introduced PEP 695 syntax: `def first[T](items: list[T]) -> T` — cleaner and more readable than the older TypeVar approach. For protocol-based structural subtyping (duck typing with type safety), use `Protocol` instead of ABCs when you don't own the classes you're typing.
The patterns that catch the most bugs in practice: annotate return types (not just parameters), use `Optional[T]` (or `T | None` in 3.10+) explicitly instead of implicit None returns, and run mypy/pyright in CI with `--strict` from day one rather than adding it later to a large codebase.