Python Type Hints: Lying to Your Future Self, But Politely

Here is a fun fact that confuses every new Python developer: this code runs perfectly.
def add(a: int, b: int) -> int:
return a + b
print(add("hello ", "world")) # hello world
Python reads your type hints, nods politely, and ignores them completely. The interpreter does not check types. The hints are, at runtime, just annotations sitting in a dictionary, like sticky notes on a fridge that nobody reads.
So why bother? Because other tools read them, and those tools are much more pedantic than Python.
Who actually reads your hints
- Your editor. Autocomplete suddenly knows that
user.should suggest.emailand not random guesses. - Type checkers like mypy or pyright. They read your whole codebase and complain about every place where the story doesn't add up.
- Future you. Six months from now,
def process(data)tells you nothing.def process(data: list[Order]) -> Invoicetells you the whole plot.

The classic bug hints catch: None
Most production bugs I've seen in Python are some variation of "this was None and nobody expected it."
def find_user(user_id: int) -> User | None:
...
user = find_user(42)
send_email(user.email) # mypy: "User | None" has no attribute "email"
The type checker forces the uncomfortable conversation early:
user = find_user(42)
if user is None:
raise UserNotFound(42)
send_email(user.email) # now it's fine
That one if is a 2 AM page you will never receive.
Where the lies creep in
Type hints are only as honest as the person writing them. Some of my favourite polite lies:
def parse(raw: str) -> dict:— technically true, spiritually useless. Use aTypedDictor a dataclass so the shape is visible.Anyeverywhere — the type system equivalent of "it's complicated."# type: ignore— a little note that says "trust me," which is exactly what people say right before something goes wrong.
from dataclasses import dataclass
from decimal import Decimal
@dataclass(frozen=True)
class Invoice:
customer_id: int
total: Decimal
currency: str = "CAD"
Now the shape of your data is documented, checked, and immutable. Your code reads like a contract instead of a rumour.
Start small
You don't have to annotate a 50,000-line project in a weekend. Add hints to new code and to functions you touch. Run mypy in CI with a lenient config, then tighten it slowly. Type hints are like flossing: annoying to start, impossible to justify skipping once you notice the difference.
And remember: Python will still let you pass a string where an int should go. The hints are a promise, not a cage. Just try to keep your promises.