Learning Goals
3 min- Add type hints to functions, variables, and collections.
- Run
mypyand read its errors. - Use
Optional,list[...],dict[...], custom types. - Adopt mypy gradually on an existing codebase.
Warm-Up · The Bug mypy Catches
5 mindef double(n: int) -> int: return n * 2 result = double("5") # mypy ERROR before running: # Argument 1 to "double" has incompatible type "str"
At runtime, double("5") returns "55" (string repeated) — a silent bug. mypy reads the hint n: int and flags the mistake statically, before the code runs and surprises you in production.
Type hints document what types each function expects and returns; mypy checks your whole program against them — a powerful static net that catches a class of bugs tests might miss. Hints are optional and don't affect runtime; mypy is the tool that enforces them.
New Concept · Hints & mypy
14 minThe hint syntax
def greet(name: str, times: int = 1) -> str: return f"Hi {name}! " * times count: int = 0 names: list[str] = ["Aisyah", "Wei Jie"] scores: dict[str, int] = {"Aisyah": 90}
Optional and unions
from typing import Optional def find(email: str) -> Optional[str]: # returns str OR None ... def parse(x: int | str) -> int: # accepts int OR str (3.10+ syntax) return int(x)
Optional[str] means "str or None" — and mypy forces you to handle the None case before using the value, catching NoneType bugs.
Run mypy
pip install mypy
mypy myfile.py
mypy mypackage/
mymod.py:8: error: Argument 1 to "double" has incompatible type "str";
expected "int" [arg-type]
Found 1 error in 1 fileThe None-safety win
def get_user(uid: int) -> Optional[dict]: ... u = get_user(1) print(u["name"]) # mypy ERROR: u could be None! # forces you to check: if u is not None: print(u["name"]) # now safe
This is mypy's most valuable catch: the "forgot it could be None" bug that causes countless production AttributeErrors.
Configure & adopt gradually
# pyproject.toml [tool.mypy] python_version = "3.12" warn_unused_ignores = true # start lenient, then tighten: # strict = true ← turn on once your codebase is annotated
On an existing project, add hints file-by-file and run mypy on just those files first. Don't flip strict = true on day one — it'll drown you in errors.
Worked Example · Type a Module & Catch Bugs
12 min# cart.py — fully annotated from typing import Optional class Cart: def __init__(self) -> None: self._items: dict[str, float] = {} def add(self, item: str, price: float) -> None: self._items[item] = price def find(self, item: str) -> Optional[float]: return self._items.get(item) def total(self) -> float: return sum(self._items.values())
# someone uses it wrong — mypy catches all of these: cart = Cart() cart.add("roti", "1.50") # error: "str" not "float" cart.add(123, 1.50) # error: "int" not "str" for item price = cart.find("nasi") # price is Optional[float] discounted = price * 0.9 # error: price could be None! # fix: if price is not None: discounted = price * 0.9
$ mypy cart.py usage.py usage.py:3: error: Argument 2 to "add" has incompatible type "str"; expected "float" usage.py:4: error: Argument 1 to "add" has incompatible type "int"; expected "str" usage.py:7: error: Item "None" of "Optional[float]" has no attribute "__mul__" Found 3 errors in 1 file
Read the diff
Three bugs caught without running anything: a price passed as a string, an item passed as an int, and the classic "multiplied something that could be None". None of these are syntax errors — Python would run them and either misbehave or crash later. mypy + type hints turn them into instant, located errors. Combined with tests, you have both static and dynamic safety nets.
Try It Yourself
13 minAdd type hints to one of your functions (args + return). Run mypy. Does it find anything?
Annotate a function, then call it with the wrong type. Confirm mypy reports it before runtime.
Write a function returning Optional[...]. Use the result without checking for None — confirm mypy flags it. Add the check.
Mini-Challenge · Make a Module mypy-Clean
8 minTake one module, fully annotate it (every function's args + return, key variables), and get mypy to report zero errors. Document any real bug the annotations exposed. Then add the mypy config to pyproject.toml.
Recap
3 minType hints (name: str, -> int, Optional[...], list[...]) document expected types; mypy statically checks your program against them, catching wrong-type and None-safety bugs before runtime. Adopt gradually, tighten to strict later. mypy (static) + tests (dynamic) together are a strong safety net. Next: combine all these tools into pre-commit hooks.
Vocabulary Card
- type hint
- An annotation declaring a variable's or function's type.
- mypy
- A static type checker that verifies code against its hints.
- Optional[T]
- Type meaning "a T or None"; mypy forces you to handle None.
- gradual typing
- Adding type hints incrementally to an existing codebase.
Homework
4 minFully annotate a real module and make it mypy-clean (zero errors). Include at least one Optional return that forces a None check. Add the mypy config to pyproject.toml. Report any genuine bug the type checker exposed.
Model on the annotated cart.py. The bug mypy most often exposes is an unchecked Optional — using a value that could be None.