Python Static Type Checking

Reference file — part of the programming-languages hub. Authored via /dr (deep-research). Not a standalone skill. Sibling topics in this family are reference files under the hubs (programming-languages, software-engineering-patterns) — not standalone skills. Cross-refs: references/python-patterns.md (PEP 695 type-hint syntax, generics — this file covers the checkers), references/pydantic-v2.md (runtime validation + the pydantic.mypy plugin), references/typescript-expert.md (the TS analog of gradual static typing).

Python Static Type Checking — mypy, Pyright, ty, Pyrefly

Python type checkers are external static-analysis tools, not part of the interpreter. The CPython runtime ignores annotations (beyond storing them in __annotations__); a separate tool reads the same annotations a human reads and proves type consistency before the code runs.

As of mid-2026 the landscape is a two-generation split: established Python-implemented checkers (mypy, Pyright) and new Rust-implemented checkers (Astral ty, Meta Pyrefly) that are 10-80x faster. Pyrefly reached stable 1.0.0 (May 2026); ty is still beta (0.x).

1. Shared foundation: gradual typing

2. The four checkers

mypy Pyright ty Pyrefly
Author community Microsoft Astral (uv/ruff) Meta
Language Python (mypyc) TS/Node Rust Rust
Maturity 2026 mature, ref impl mature beta 0.x stable 1.0.0
Unannotated funcs skips checks checks checks
Inference aggressive list[int] conservative list[Unknown] conservative, gradual guarantee aggressive list[int]
Config table [tool.mypy]/mypy.ini [tool.pyright] [tool.ty] [tool.pyrefly]
Extensibility plugins + stubs stubs only stubs stubs
LSP none built-in Pylance (best) full LSP full LSP + infer-to-source
Speed baseline varies fastest ~80x ~2-3x slower than ty
Novel early PEP intersection & negation types strong generic inference

CLI: mypy <path>, pyright <path>, ty check <path>, pyrefly check <path>.

3. Inference divergence (the key behavioral difference)

For my_list = []; my_list.append(1):

Generics: c: C[int] = C() reveals C[int] (Pyrefly) vs C[Unknown] (ty). mypy/Pyrefly catch more bugs in loose code but more false positives; Pyright/ty cause fewer surprises during adoption. reveal_type(x) prints inferred type during a check.

4. mypy (reference impl)

[tool.mypy]
strict = true
plugins = ["pydantic.mypy"]
[[tool.mypy.overrides]]
module = ["legacy.*"]
disallow_untyped_defs = false
ignore_missing_imports = true

5. Pyright (conservative, IDE-first)

6. ty (Astral, beta)

7. Pyrefly (Meta, stable 1.0.0)

8. Choosing

9. Migrating an untyped codebase

  1. Start permissive, ratchet strictness from a clean baseline.
  2. Strict-by-default, loose-for-legacy via per-module overrides.
  3. Fix missing third-party types: install types-<pkg> or scope ignore_missing_imports — don’t blanket-ignore.
  4. Auto-annotate with pyrefly infer, then review.
  5. Ship py.typed (PEP 561) so downstream trusts your inline types.
  6. Gate in CI once clean; track error count downward.

10. Anti-patterns

11. Troubleshooting

References