A typing taster, not a course in static typing religion. Type hints document intent, improve autocomplete, and catch some bugs early. They do not change how Python runs unless you add a separate type checker.
Basic annotations
Annotate parameters and return values on public functions. Skip obvious script glue if it adds noise.
def add_vat(net: float, rate: float = 0.20) -> float:
return round(net * (1 + rate), 2)
name: str = "Alice"
count: int = 0
Collections and optional values
Use standard collection types from typing or, in Python 3.9+, built-in generics.
from typing import Optional
def first_email(contacts: list[dict[str, str]]) -> Optional[str]:
if not contacts:
return None
return contacts[0].get("email")
Optional[str] means str | None. Either spelling is fine; be consistent within a project.
TypedDict for structured dicts
When a dict has known keys, say so. It helps more than bare dict.
from typing import TypedDict
class UserRow(TypedDict):
id: int
email: str
active: bool
def deactivate(user: UserRow) -> UserRow:
user["active"] = False
return user
Running mypy or pyright lightly
Install a checker in dev dependencies and run it in CI when you are ready. Start with your package, not every script at once.
pip install mypy
mypy src/mytool
You will get false positives at first. Fix real issues; add targeted # type: ignore comments sparingly when a library lacks stubs.
What not to do
- Do not annotate every local variable – clutter without benefit.
- Do not chase perfect generics in a 40-line script.
- Do not treat hints as runtime validation – use explicit checks for user input.
- This is not TypeScript – embrace duck typing where it already works.
Hints alongside dataclasses
Dataclasses and type hints complement each other neatly.
from dataclasses import dataclass
@dataclass
class Invoice:
number: int
total: float
paid: bool = False
Use hints where they pay rent: public APIs, data shapes, and code you edit often. Next: exceptions and logging as a system, not a scatter of bare except: blocks.

