Interview frequency: …
Short answer
Concentrate precise types on public interfaces and system boundaries (public module functions, domain types, protocols), and leave local variables to automatic inference.
Explanation
The best annotation is one that clarifies a contract or invariant that both reader and tool benefit from: what the public function accepts and returns, and where None may appear. Use NewType to distinguish identifiers of the same underlying type such as UserId and OrderId, and write public signatures with abstract types such as Sequence and Mapping instead of list and dict so they accept wider input. Avoid complicated generics for every local variable; the reading and maintenance cost should be lower than the benefit.
Code example
from collections.abc import Sequence
from typing import NewType
UserId = NewType("UserId", int)
OrderId = NewType("OrderId", int)
def cancel(order_id: OrderId, by: UserId) -> None: ...
def average(values: Sequence[float]) -> float:
return sum(values) / len(values)
cancel(UserId(7), OrderId(3)) # mypy Detects swapped parametersCommon mistakes
- Using list[int] in public parameters instead of Sequence[int], which needlessly rejects tuples.
- Passing identifiers of different kinds as plain int so they get mixed up without anyone noticing.