Type Hints & Static Typing
🏷️ Type Hints & Static Typing
typing module's building blocks, mypy as an external static checker, Protocol for structural typing, and generics.
✍️ Basic Type Hints
A type hint is written as name: Type for a variable or parameter, and -> Type after a function's parameter list for its return type. This looks similar to Kotlin's own type annotations, but with one crucial difference — see the warning below.
Unlike Go or Kotlin, where the compiler rejects a type mismatch before the program can even run, Python's type hints are pure documentation as far as the language itself is concerned — the interpreter never checks them. add("hello", "world") above happily runs and string-concatenates, despite the hints clearly saying both parameters should be int. Catching this kind of mismatch requires a separate tool — mypy, covered next.
🧱 typing Module Building Blocks
list[str], dict[str, int], and similar generic-collection hints use Course 1's own collection types (Chapter 6, Chapter 7) directly as the annotation. X | None (modern syntax) or Optional[X] (the older typing module form, equivalent) marks a value that might legitimately be missing — the same idea as the Option<T>-style types more strictly-typed languages use, but here purely advisory rather than compiler-enforced.
🔍 mypy
mypy (installed via pip, per Course 1, Chapter 9) reads a script's type hints and statically checks whether the code actually respects them — without ever running the code. This is what gives Python-with-hints something closer to Go's or Kotlin's compile-time safety net: run mypy as a separate step (often wired into CI, per the site's own CI/CD Pipelines course), catch mismatches before deployment, while the interpreter itself stays exactly as permissive as it's always been.
🦆 Protocol (Structural Typing)
Course 3, Chapter 1's ABC is nominal typing — a class must explicitly inherit from the abstract base to count as satisfying it. Protocol is structural typing — any class with a matching honk() method satisfies Honker, whether or not it ever mentions Honker by name. This formalizes Python's long-standing "duck typing" culture (if it walks like a duck and honks like a car horn...) into something mypy can actually check statically.
🧬 Generics
TypeVar lets a function or class be generic over whatever type it's actually given — first() works correctly on a list[int] or a list[str] alike, and mypy tracks that the return type always matches whatever T resolved to for that specific call, the same generic pattern Go's and Kotlin's own generics (already covered elsewhere on the site) provide, just checked externally rather than by the language itself.
Type Enforcement: Go vs Kotlin vs Python
| Language | When types are checked |
|---|---|
| Go | Compile time, mandatory — a type mismatch is a compile error, the program cannot even build. |
| Kotlin | Compile time, mandatory — same guarantee as Go, enforced by the compiler before any code runs. |
| Python | Never, by the language itself — hints are optional documentation; mypy can check them separately, entirely opt-in, and skipping it (or running mismatched code anyway) is always possible. |
This is the single biggest mindset shift in this chapter: in Go or Kotlin, "it compiled" is itself a real type-safety guarantee. In Python, "it has type hints" guarantees nothing on its own — the guarantee only exists if mypy (or an equivalent) is actually run, and even then, only for the code paths it checks.
Type hints
name: Type, -> Type — documentation only, not enforced by the interpreter.
mypy
A separate static checker — run it to actually catch type mismatches before runtime.
Protocol
Structural typing — matches by shape (duck typing), no inheritance required, unlike ABC.
TypeVar / generics
A function/class that works across types, with the relationship tracked by mypy.
💻 Coding Challenges
Challenge 1: Annotate a Function
Take the fizzbuzz(n) function from Course 2, Chapter 8's testing challenge, and add full type hints: n should be typed as int, and the return type should reflect that it always returns a str.
Goal: Practice adding basic type hints to an existing, already-working function.
Challenge 2: A Protocol for Comparable Items
Define a Protocol called HasArea with a method area(self) -> float. Write a function describe(shape: HasArea) -> str that returns f"Area is {shape.area()}". Call it with an instance of the Square class from Course 2, Chapter 2 — it should work despite Square never mentioning HasArea.
Goal: Practice structural typing with Protocol and confirm it works without inheritance.
Challenge 3: A Generic last() Function
Using TypeVar, write a generic function last(items: list[T]) -> T that returns the last item of any list. Call it once with a list[int] and once with a list[str], printing both results.
Goal: Practice writing a generic function with TypeVar, mirroring this chapter's first() example.
🎯 What's Next
Next chapter: Packaging & Distribution — pyproject.toml, setuptools/poetry, and publishing to PyPI.