Guides Long read Mypy

Are types just documentation?

Type hints, Protocol, and static analysis with mypy.

18 January 2026 21 min read
Share
X in

Introduction

Python is dynamically typed, but type hints from PEP 484 have become a powerful tool for catching errors during development and improving readability. Static analyzers like mypy, pyright, and pyre report type mismatches before runtime.

This guide covers basic annotation syntax, collections.abc types, structural subtyping with Protocol, generic containers, and mypy configuration. Type hints are not enforced at runtime unless you add optional validation with pydantic or beartype.

Basic Type Hints

Annotate function parameters and return values: def greet(name: str) -> str:. Variable annotation is x: int = 0. Python 3.10+ uses built-in generic syntax list[str], dict[str, int]; older code imports List, Dict from typing.

Optional[str] and str | None (3.10+) express nullable types. Union[int, str] accepts multiple types. Any disables type checking; avoid it when possible.

from typing import Optional

def find_user(user_id: int) -> Optional[dict[str, str]]:
    if user_id < 0:
        return None
    return {"id": str(user_id), "name": "Ali"}

Protocol and Structural Subtyping

Protocol brings duck typing to static analysis: any class with the required methods satisfies the protocol without inheritance. This simplifies fakes in tests and loosely coupled design.

@runtime_checkable allows isinstance(obj, Readable) at runtime. Protocols are more flexible than ABCs; they provide type safety without wrapping third-party classes.

from typing import Protocol

class Readable(Protocol):
    def read(self, n: int = -1) -> bytes: ...

def load(source: Readable) -> bytes:
    return source.read()

Generic and TypeVar

Generic classes and functions carry type parameters: class Stack[T]: ... TypeVar supports bounds: T = TypeVar('T', bound=Comparable). ParamSpec and TypeVarTuple (3.11+) express decorator and callback types more accurately.

mypy applies invariant container rules for generics: list[Dog] is not assignable to list[Animal]. Use Sequence[Animal] when covariance is needed. This distinction is the source of many collection API type errors.

  • list[T], dict[K, V]: generic collections
  • TypeVar: type parameter and bounds
  • Protocol: structural interface
  • TypedDict: dictionary shape definition

mypy Configuration

Define strictness in pyproject.toml or mypy.ini. disallow_untyped_defs requires types on all functions. ignore_missing_imports is a temporary fix for missing third-party stubs; prefer stubs packages like types-requests.

mypy --strict is the strictest mode; for gradual adoption of an existing codebase use per-module # mypy: disable-error-code or per-file overrides. Running mypy on every commit via pre-commit prevents regressions.

# pyproject.toml
[tool.mypy]
python_version = "3.12"
strict = true
warn_return_any = true
disallow_untyped_defs = true

TypedDict, Literal, and overload

TypedDict defines the shape of dicts with fixed keys; a lightweight alternative for API response models. Literal['GET', 'POST'] expresses fixed string value sets. @overload declares multiple signatures for the same function to static analysis.

Final and ClassVar mark immutability at class and module level. NewType creates branded types: UserId = NewType('UserId', int) prevents confusion with plain int.

from typing import TypedDict, Literal

class UserDict(TypedDict):
    id: int
    name: str

def request(method: Literal["GET", "POST"], url: str) -> bytes: ...

Practical Integration

FastAPI and Pydantic validate types at runtime; mypy adds a second layer of safety. SQLAlchemy 2.0 Mapped[T] makes ORM models type-safe. A mypy pytest plugin can include type errors in the test suite.

Gradual adoption: type public APIs first, then spread to internal modules. Libraries marked py.typed improve mypy compatibility when types-* stubs are missing. Ruff ANN rules lint missing annotations.

  1. Start typing from the public API outward
  2. Add mypy to CI and pre-commit
  3. Use types-* stubs packages
  4. Simplify test doubles with Protocol

Conclusion

Type hints do not slow Python down; they improve IDE completion, refactor safety, and early error detection. Combined with mypy, they balance the flexibility of a dynamic language with the safety of a static one.

Advanced features like Protocol and Generic improve maintainability in large codebases. Making the type system a team standard shortens onboarding and reduces production bug rates.