Skip to content
intermediate Phase 12 · Python Type Hints & Quality

Type Hints & Annotations

Add type annotations to Python code with typing module and mypy.

1h
0 problems
Topic Progress 0%

Annotations That Express Intent

Annotations That Express Intent

Annotations document the types functions expect and return; Python stores them in __annotations__ but does not enforce them at runtime.

def retry(fn: Callable[[], T], attempts: int = 3) -> T: ...
counts: dict[str, int] = {}          # PEP 585 builtins, Python 3.9+
opt: str | None = None               # PEP 604 unions, Python 3.10+

Prefer builtin generics (list[int], tuple[str, int]) over legacy typing.List in modern code, use Optional[X]/X | None for possibly-absent values, and -> None for procedures. Annotations are the contract static checkers read — they do nothing when the program runs, so treat them as documentation plus verification, not protection.

Checking with mypy

Checking with mypy

Add a configuration block and run the checker in CI alongside tests:

# pyproject.toml
[tool.mypy]
python_version = "3.12"
strict = true            # enables disallow_untyped_defs, warn_return_any, ...
files = ["src"]
mypy src          # type-check the package
mypy --follow-imports=skip module.py   # check one file with untyped deps

mypy is gradual: untyped functions are checked leniently unless --strict or per-module overrides demand annotations. Adopt incrementally — start with check_untyped_defs, then tighten modules you actively change. cast(T, value) and # type: ignore[assignment] are escape hatches; each one should carry a comment justifying it.

Pitfalls and Interview Angles

Pitfalls and Interview Angles

Annotations are erased from runtime behavior. Passing a wrong int where str is declared raises no error until some downstream string operation fails. Validate untrusted input with pydantic, dataclasses, or hand-written checks; let mypy guard internal logic.

Any spreads silently. One Any in a signature disables checking for every caller that value touches. Prefer object (must narrow before use), TypeVar, or a Protocol.

Interview angle: explain gradual typing — untyped legacy code coexists with strict modules — then contrast static checking with runtime validation and name a bug each one would catch that the other misses.

Quiz

1. What happens at runtime to a function annotated as def f(x: int) -> str when called with f("a")?

Question 1 options

2. Which typing approach is current for a dict of string keys and list values?

Question 2 options

3. Why is returning Any dangerous in strict mode?

Question 3 options

Flashcards

Question

Gradual typing

Answer

A system where typed and untyped code interoperate; checking strength is configurable per module.

Question

Optional[X] vs X | None

Answer

Same meaning: value is X or None. The | syntax needs Python 3.10+ (or future import).

Question

mypy --strict

Answer

Enables full checking: annotated defs, no Any leaks, warn_return_any, and more.

Question

Runtime vs static validation

Answer

Annotations are static only; parse untrusted data with validators before trusting it.

Revision Notes

Key Takeaways

  • 1. Annotations document contracts for tools; the interpreter never enforces them
  • 2. Built-in generics and unions replaced typing.List/Optional for Python 3.9+/3.10+ code
  • 3. Keep Any rare and validate external data at runtime with a real validator

Interview Tips

  • • Say explicitly that type hints are erased from enforcement and name the tool that reads them instead
  • • Show a gradual-typing adoption plan: strict config, new code strict, legacy modules tightened over time

Cheat Sheet

def f(x: int) -> str; list[int]/dict[str,int] (3.9+), X | None (3.10+); annotations not enforced; mypy --strict in CI; Any disables checks.

Continue Learning

Before You Start

  • No prerequisites listed.

Next Topics