Type Hints

Annotations for documentation, tooling, and catching bugs before runtime

Why

Modern Python codebases — especially at FAANG and AI companies — use type hints extensively. They power IDE autocompletion, enable static analysis (mypy, pyright), and make function signatures self-documenting. They catch type errors at development time that would otherwise surface as cryptic runtime bugs.

Modern Python codebases — especially at AI and tech companies — expect annotated code. Type hints signal production hygiene and make Optional, Protocol, and generics part of everyday vocabulary.

Intuition

Type Hints are annotations — Python itself ignores them at runtime entirely. They exist for humans and for tools. Think of them as a machine-readable docstring that tools can verify. Protocol is Python's structural typing — "any class that has these methods" — making duck typing explicit and tool-checkable.

Explanation
1. Basic annotations and Optional / Union

Annotate function parameters and return types with their expected types. Optional[X] signals a value that may be absent; the | union syntax allows multiple types. Python ignores annotations at runtime — they exist for tools and humans.

from typing import Optional def greet(name: str, repeat: int = 1) -> str: return name * repeat # Optional[X] == X | None — value or None def find(items: list[str], target: str) -> Optional[int]: try: return items.index(target) except ValueError: return None # Python 3.10+ union shorthand def process(value: int | str | None) -> str: return str(value) if value is not None else "" # Built-in generics (Python 3.9+ — no import needed) def summarise(data: list[float]) -> dict[str, float]: return {"mean": sum(data) / len(data)}

Optional[X] is shorthand for X | None — use it whenever a value may be absent. Built-in generics like list[int] and dict[str, float] need no import in Python 3.9+.

2. TypeVar and Protocol

TypeVar lets you write generic functions where the input and output types stay consistent. Protocol formalises duck typing — any class with the required methods satisfies it, no inheritance needed.

from typing import TypeVar, Protocol from collections.abc import Sequence T = TypeVar("T") def first(items: Sequence[T]) -> T: # works for any sequence type return items[0] # Protocol — structural subtyping (duck typing, made explicit) class Drawable(Protocol): def draw(self) -> None: ... class Circle: def draw(self) -> None: # no inheritance needed print("circle") def render_all(shapes: list[Drawable]) -> None: for s in shapes: s.draw() render_all([Circle()]) # Circle satisfies Drawable structurally

Protocol is duck typing made tool-checkable. Any class with the required methods satisfies the protocol — no inheritance, no registration. This is how Python's own built-in protocols work: anything with __len__ is a Sized.

3. Callable, TypedDict, Final

Annotations for three advanced patterns: typing functions passed as values, adding type information to plain dicts, and marking constants that should never be reassigned.

from collections.abc import Callable from typing import TypedDict, Final def apply(fn: Callable[[int], int], value: int) -> int: return fn(value) # TypedDict — structured dict with known keys and types class UserRecord(TypedDict): id: int name: str email: str # Final — this variable should never be reassigned MAX_RETRIES: Final = 3

Callable[[ArgTypes], ReturnType] types functions passed as values. TypedDict adds type information to a plain dict — lighter than a full class, works anywhere a dict is expected. Final marks a name as a constant; type checkers will flag any reassignment.