Python type hints have a reputation problem: they look like ceremony borrowed from Java. In practice, a small subset of typing features catches a large share of real bugs: the None that sneaks through three layers, the dictionary key that was renamed in one place, the function that sometimes returns a list and sometimes a generator.
The 20 percent that does 80 percent of the work
- Annotate every function signature, including parameters and return type. Skip local variables; inference handles them.
- Use X | None instead of implicit optionality, and let the checker force you to handle the None branch.
- Replace dict payloads that cross function boundaries with dataclasses or TypedDict — named fields are refactorable, string keys are not.
- Use Literal for small closed vocabularies like modes and statuses.
from dataclasses import dataclass
from typing import Literal
OrderStatus = Literal["pending", "paid", "shipped", "cancelled"]
@dataclass(frozen=True)
class Order:
id: int
status: OrderStatus
total_cents: int
def refundable(order: Order) -> bool:
# The checker knows every possible status — a typo here is an error,
# not a silent False at runtime.
return order.status in ("paid", "shipped")The None bug, extinct
The most common production exception in Python codebases is AttributeError on None. With strict optional checking, a function that can return None forces every caller to decide what happens in that case — at review time, not at 2 a.m. This one setting justifies the entire typing effort.
def find_user(email: str) -> User | None: ...
user = find_user(email)
send_welcome(user) # type error: User | None is not User
if user is not None:
send_welcome(user) # narrowed — checker and human both satisfiedAdopting types in an existing codebase
Do not annotate everything at once. Turn the checker on with lenient settings, type the modules you touch as you touch them, and ratchet strictness per package. New code gets full annotations from day one; legacy code earns them when it changes. Within a few months the strict zone covers everything that matters.
Types are executable documentation: the only comments that fail the build when they go stale.
- #Python
- #Type Hints
- #Code Quality