State-of-the-art comparison of the five Python type checkers as of June 2026: mypy, basedpyright, ty, pyrefly, and Zuban. The Rust newcomers (ty, pyrefly, Zuban) have made mypy’s speed indefensible, but mypy’s plugin ecosystem and pyright/basedpyright’s spec conformance remain real moats. Conformance numbers move week-to-week, so re-verify against the live python/typing dashboard before citing. Related: Experience using UV (ty shares Astral’s uv/Ruff lineage), Parse, Don’t Validate (the value of pushing invariants into types).
Visual report: python-type-checkers-2026.html — open in a browser for the styled broadsheet version (gauges, deep-dive probes, gap tracker).
Legend for matrices below: ✅ full · ~ partial/buggy · ❌ none. Every cell is resolved against live conformance result files + issue trackers (no unknowns remain).
At a glance
| mypy | basedpyright | ty | pyrefly | Zuban | |
|---|---|---|---|---|---|
| Vendor | python/ org (Lehtosalo et al.) | DetachHead (solo) | Astral (Ruff/uv) | Meta | Dave Halter (Jedi author, solo) |
| Language | Python (+ mypyc→C) | TypeScript/Node | Rust (Salsa) | Rust | Rust |
| Latest ver. | 2.1.0 (2026-05-11) | 1.39.8 / pyright 1.1.410 (2026-06-14) | 0.0.49 (2026-06-12) | 1.0.0 (2026-05-12) | 0.8.2 (2026-06-09) |
| Maturity | Mature reference | Mature (inherits pyright) | Beta (0.0.x) | Stable (1.0) | Beta (pre-1.0) |
| License | MIT | MIT | MIT | MIT | AGPL-3.0 + paid commercial |
| Lineage | The original (~2012) | Fork of MS pyright | New | Rust rebuild of Pyre (OCaml) | Independent; folds in Jedi |
Conformance (official suite)
Live python/typing dashboard as of June 2026 (141 tests; dashboard versions: ty 0.0.48, pyrefly 1.0.0, zuban 0.7.2, pyright 1.1.409, mypy 2.1.0):
| Checker | Live (Jun 2026) | Mar 2026 same-day snapshot |
|---|---|---|
| Zuban | 99.3% (140/141) | 96.4% |
| basedpyright (= pyright) | 95.0% (134/141) | 97.8% |
| pyrefly | 92.2% (130/141) | 87.8% |
| ty | 70.9% (100/141) | 53.2% |
| mypy | 58.9% (83/141) | 58.3% |
ty climbed ~18 points since March and now out-conforms mypy; Zuban has overtaken pyright at the top of the live dashboard (the March same-day run had it the other way). Caveat: conformance ≠ usefulness. Note the dashboard trails releases slightly: pyrefly’s entry is still 1.0.0 and ty’s is one patch behind (0.0.48 vs 0.0.49). Sources: Pyrefly conformance comparison (Mar 2026), live dashboard file tally (computed 2026-06-16).
Performance
All vendor numbers are upper bounds (hardware/parallelism cherry-picked). The three Rust checkers are roughly an order of magnitude faster than mypy/pyright on cold runs.
| Cold full-check | Incremental/LSP | Parallel? | |
|---|---|---|---|
| mypy | Slowest (baseline); home-assistant ~45s | dmypy daemon (~10x repeat); no real LSP | New --num-workers (experimental, ~1.2–5x) |
| basedpyright | ~2–3x slower than mypy | Fast watch mode | No (single-threaded Node) |
| ty | ~2.2s home-assistant (~20x mypy); “10–60x” claimed | ~4.5ms PyTorch (best measured) | Yes |
| pyrefly | ~1.85M LoC/s claimed; Instagram 20M lines ~30s | <10ms rechecks | Yes |
| Zuban | ”20–200x faster than mypy” (vendor, no published harness) | 97ms first-completion (fastest in Posit’s test) | Yes |
Sources: Astral ty blog, Pyrefly speed/memory, pydevtools comparison.
Fine-grained capability matrix
Grounded in the live python/typing conformance results + each project’s issue tracker (June 2026). Columns: mypy · basedpyright · ty · pyrefly · Zuban.
A. Core type system
ty’s holes (variadics, generic aliases, protocols, variance-inference) live here, not in the headline conformance %.
| Feature | mypy | based | ty | pyrefly | Zuban |
|---|---|---|---|---|---|
| Return-type inference (unannotated) | ~ | ✅ | ~ | ✅ | ✅ |
| TypeVar bounds / constraints / variance | ✅ | ✅ | ~ | ✅ | ✅ |
| PEP 695 syntax | ✅ | ✅ | ✅ | ✅ | ✅ |
| TypeVar defaults (696) | ✅ | ✅ | ~ | ✅ | ✅ |
| Generic type aliases | ✅ | ✅ | ❌ | ✅ | ✅ |
| ParamSpec + Concatenate (612) | ✅ | ✅ | ~ | ~ | ✅ |
| ↳ .args/.kwargs components | ✅ | ✅ | ~ | ✅ | ✅ |
| TypeVarTuple + Unpack (646) | ✅ | ✅ | ❌ | ✅ | ✅ |
| Self type (673) | ✅ | ✅ | ~ | ~ | ✅ |
| Protocols: structural | ✅ | ✅ | ~ | ✅ | ✅ |
| Protocols: @runtime_checkable | ✅ | ✅ | ❌ | ✅ | ✅ |
| Protocols: recursive | ✅ | ✅ | ❌ | ✅ | ✅ |
| Overload resolution | ✅ | ✅ | ✅ | ✅ | ✅ |
| ↳ overlap detection | ✅ | ✅ | ❌ | ✅ | ✅ |
| ↳ impl-consistency | ✅ | ✅ | ✅ | ✅ | ✅ |
| ↳ decorated overloads | ✅ | ✅ | ~ | ✅ | ✅ |
| Literal (586) / LiteralString (675) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Final / ClassVar / Annotated | ✅ | ✅ | ✅ | ✅ | ✅ |
| NewType / NamedTuple | ✅ | ✅ | ~ | ✅ | ✅ |
| Enum (expansion / exhaustiveness) | ✅ | ✅ | ~ | ✅ | ✅ |
B. Narrowing & flow
ty’s TypeIs conformance test is currently regressing at 0.0.48 even though TypeGuard passes.
| Feature | mypy | based | ty | pyrefly | Zuban |
|---|---|---|---|---|---|
| Core (isinstance / None / truthiness / assert / walrus) | ✅ | ✅ | ✅ | ✅ | ✅ |
| TypeGuard (647) | ✅ | ✅ | ✅ | ✅ | ✅ |
| TypeIs (742) | ✅ | ✅ | ~ | ✅ | ✅ |
| match-statement narrowing | ✅ | ✅ | ~ | ~ | ~ |
| tuple-length narrowing | ✅ | ✅ | ❌ | ✅ | ✅ |
| Reachability / unreachable code | ✅ | ✅ | ✅ | ✅ | ~ |
| Exhaustiveness via assert_never | ✅ | ✅ | ~ | ✅ | ✅ |
C. TypedDict & dataclasses
The newest TypedDict PEPs (728 closed, 692 kwargs) show the widest spread. ty closed PEP 728 on 2026-06-12; mypy still tracks it open.
| Feature | mypy | based | ty | pyrefly | Zuban |
|---|---|---|---|---|---|
| TypedDict class + functional syntax | ✅ | ✅ | ✅ | ✅ | ✅ |
| Required / NotRequired (655) | ✅ | ✅ | ✅ | ✅ | ✅ |
| ReadOnly (705) | ✅ | ✅ | ✅ | ✅ | ✅ |
| PEP 728 closed / extra_items | ❌ | ✅ | ✅ | ✅ | ✅ |
| Unpack **kwargs TypedDict (692) | ~ | ✅ | ❌ | ✅ | ✅ |
| dataclasses + dataclass_transform (681) | ✅ | ✅ | ✅ | ✅ | ✅ |
| slots detection | ✅ | ~ | ❌ | ✅ | ✅ |
| @override (698) | ✅ | ✅ | ~ | ✅ | ✅ |
| @deprecated (702) | ✅ | ✅ | ~ | ✅ | ✅ |
D. Framework & library support
Mechanism is everything. mypy = runtime plugins (django/pydantic/sqlalchemy). pyrefly = native special-casing for pydantic/django. basedpyright/ty/Zuban have no plugin system — only dataclass_transform-based libs (modern pydantic, attrs, SQLAlchemy 2.0) and typeshed stubs work; anything needing a plugin (django-stubs ORM, classic attrs) degrades.
| Library | mypy | based | ty | pyrefly | Zuban |
|---|---|---|---|---|---|
| pydantic v2 | ✅ plugin | ✅ dc_transform | ~ stubs | ~ native | ~ dc_transform |
| Django ORM | ✅ plugin | ~ stubs | ❌ | ~ native | ❌ planned |
| SQLAlchemy 2.0 (Mapped[]) | ✅ native | ✅ dc_transform | ~ stubs | ~ stubs | ❌ |
| attrs | ✅ built-in | ✅ dc_transform | ❌ | ~ stubs | ~ dc_transform |
| pytest fixtures | ❌ | ❌ | ❌ | ~ | ❌ planned |
| FastAPI | ✅ via pydantic | ✅ | ~ | ~ | ~ |
| numpy / pandas (stubs) | ~ | ~ | ~ | ✅ | ~ |
| functools.partial | ✅ | ✅ | ❌ | ~ | ~ |
| General plugin system | ~ experimental | ❌ | ❌ | ❌ | ❌ not planned |
E. Adoption & ergonomics
Incremental adoption is the real-world deciding factor. basedpyright and pyrefly have first-class baselines/suppression; ty has neither yet.
| Feature | mypy | based | ty | pyrefly | Zuban |
|---|---|---|---|---|---|
| Baseline / ignore-existing-errors | ~ (basedmypy fork) | ✅ baseline.json | ❌ | ✅ suppress | ~ |
| strict preset | ✅ | ✅ +all | ❌ | ✅ | ✅ mypy-compat |
| Per-rule severity config | ✅ | ✅ | ✅ | ✅ | ✅ |
| Inline suppression w/ rule codes | ✅ | ✅ | ✅ | ✅ | ✅ |
| Unused-ignore detection | ✅ | ✅ | ✅ on-by-default | ~ native-only | ❌ |
| Migrate-from-mypy/pyright tooling | — | ❌ | ❌ | ✅ init | ✅ drop-in |
| First-class LSP | ❌ | ✅ | ✅ | ✅ | ~ rough |
| Parallel checking | ~ experimental | ❌ | ✅ | ✅ | ✅ |
Unused-ignore nuance: ty ships unused-ignore-comment at default warn (on by default — more aggressive than mypy’s off-by-default --warn-unused-ignores). pyrefly has a native unused-ignore error kind (default-off) for # pyrefly: ignore only; the # type: ignore variant is still open (#3766, #3784). Zuban does not report unused # type: ignore yet.
Gap tracker (open issues behind the ❌/~)
ty (astral-sh/ty — longest list, beta): TypeVarTuple #156 (biggest hole) · generic aliases #1851 · overload overlap #103 · decorated overloads #2278 · tuple-length narrowing #560 / match #561 · Unpack kwargs #1746 · slots #3094 · pydantic #2403 / django #1018 / attrs #2404 · pytest #1986 · functools.partial #1536 · baseline #1074 · strict #527 · PEP 723 #691.
pyrefly (facebook/pyrefly — mostly framework depth): ParamSpec Concatenate name-loss #3706 · match-on-call narrowing #2858 · pydantic frozen #3663 / alias_generator #2942 · Django reverse rels #2071 / QuerySet #3648 · SQLAlchemy mapped_column #1600 · attrs field #1825 / private-attr #2053 · pytest fixtures #3776 · functools.partial #3175 · plugin/library API #22 · unused # type: ignore #3766 / #3784.
basedpyright (DetachHead/basedpyright + microsoft/pyright — architecture, not bugs): no plugin system by design, pyright #1782 / based #22 → blocks pydantic-deep #868 · pytest #311 · PEP 728 Unpack+extra_items edge #10352 · no global config #380. Django ORM / numpy depth are stub-bound, not engine bugs.
mypy (python/mypy — spec-lag, not architecture): PEP 728 closed TypedDict #18176 · Unpack kwargs #14697 · pytest fixtures (no plugin, manual typing) · no native baseline (only basedmypy fork) · plugin API powerful but explicitly experimental / no back-compat guarantee.
Zuban (zubanls/zuban — small project, gaps cited to docs/roadmap): unused # type: ignore not yet reported (docs) · no general plugin system (“not planned”) · Django plugin (roadmap: “next planned”) · pytest planned not shipped · SQLAlchemy / classic attrs need a plugin Zuban won’t load · functools.partial stub-driven (no plugin). Sub-features once marked unknown now verified ✅ via live conformance files (recursive protocols, overload overlap, tuple-length narrowing, assert_never exhaustiveness, PEP 728, slots all Pass at Zuban 0.7.2).
Feature deep-dive
How the same probe behaves across all five — where a shared ✅ hides opposite behaviour. (See Parse, Don’t Validate for why inference precision matters.)
i. Inference on unannotated code
Probe: x = [1, 2, 3] then x.append("foo").
| Checker | infers x | verdict on append("foo") |
|---|---|---|
| mypy | list[int] | ❌ error — but unannotated def bodies are skipped unless --check-untyped-defs |
| basedpyright | list[int] | ❌ error — plus reportAny flags every Any usage site |
| ty | list[Unknown] | ✅ accepted — the gradual guarantee; deliberately, can be unsound (#1248) |
| pyrefly | list[int] | ❌ error — aggressive inference, finds bugs with zero annotations |
| Zuban | list[int] | ❌ error — zmypy mirrors mypy, zuban check mirrors pyright |
ty alone accepts the bad append: its principle is that removing an annotation must never create an error, so the literal infers to list[Unknown]. Two caveats: no checker synthesises parameter types from call sites (unannotated params are gradual at the boundary in all five — the difference is locals/returns and whether the body is checked at all), and ty’s stance is a real soundness cost on container literals, not just conservatism.
ii. Type narrowing
Probes: match get_value(): (subject is a call), if len(t) == 2:, intersection/negation A & ~B.
| Checker | match on call | tuple-length | TypeGuard / TypeIs | standout |
|---|---|---|---|---|
| basedpyright | ✅ | ✅ | ✅ / ✅ two-sided | most complete; reportUnreachable errors on dead branches |
| mypy | ✅ | ✅ | ✅ / ✅ | narrowing reworked in 1.20 (“more aggressive, consistent, correct”) |
| ty | ~ | ❌ (#560) | ✅ / ~ regressing | unique: intersection & negation narrowing (A & ~B) |
| pyrefly | ~ (#2858) | ✅ | ✅ / ✅ | “flow types”; stricter — enforces protocol param-name matching |
| Zuban | ✅ | ✅ | ✅ / ✅ | mode-dependent (zmypy = mypy, check = pyright) |
pyright/basedpyright is the completeness leader. Surprises: ty uniquely has intersection/negation narrowing yet lacks tuple-length, and its TypeIs is currently regressing while TypeGuard passes (the opposite of what you’d guess); pyrefly trips when the match subject is a call (bind it to a variable first).
iii. Overloads
| Checker | resolution | overlap | consistency | performance |
|---|---|---|---|---|
| basedpyright | ✅ | ✅ | ✅ | the reference (Traut edits the spec); stricter than mypy — code passing mypy can fail here |
| mypy | ✅ | ✅ | ✅ | slow — resolution is combinatorial; overload-heavy code is mypy’s classic hotspot |
| ty | ✅ | ❌ (#103) | ✅ | only matches mypy on overload-heavy SQLGlot, doesn’t beat it |
| pyrefly | ✅ | ✅ | ✅ | clean resolution; all overload conformance tests pass |
| Zuban | ✅ | ✅ | ✅ | zmypy reproduces mypy’s overlap errors in mypy’s format |
pyright is the de-facto reference and strictest; ty’s one clear gap is overlap detection. Overloads are the rare place the Rust speed lead collapses — resolution is combinatorial for everyone, so ty only matches mypy on overload-dense code.
iv. pydantic & frameworks
Three-tier mechanism ladder: plugin (mypy) > native special-casing (pyrefly, pydantic + django only) > dataclass_transform + stubs (basedpyright / ty / Zuban).
| Checker | mechanism | BaseModel init | validators / computed | Settings() | Django ORM |
|---|---|---|---|---|---|
| mypy | plugin | ✅ | ✅ | ✅ | ✅ |
| pyrefly | native | ✅ | ~ | ~ | ~ (ORM gaps #2071) |
| basedpyright | dc_transform | ✅ | ❌ | ~ | ~ stubs |
| ty | dc_transform | ✅ | ❌ | ❌ (#1070) | ❌ |
| Zuban | dc_transform | ✅ | ❌ | ~ | ~ stubs |
The bottom tier all get the constructor and field types right for free; what they cannot reach is runtime-semantic modelling: validator coercion (a validator taking str and storing int is invisible), a bare @computed_field loses recognition entirely, ORM query reshaping, and env-var population — so ty literally errors Settings() as missing a required api_key. This is mypy’s last, strongest moat.
IDE / LSP
- mypy — no first-class LSP; batch CLI + dmypy, editors glue via pylsp-mypy / dmypy-ls (slow first run). Weakest interactive story.
- basedpyright — full open-source LSP; re-implements Pylance-exclusive features (inlay hints, semantic tokens, docstring completion, import code-actions) that Microsoft locks to proprietary Pylance. Default in Zed.
- ty — LSP-first architecture (Salsa); auto-import, inlay hints. Official VS Code ext, PyCharm/Neovim.
- pyrefly — comprehensive LSP; default checker in PyCharm 2026.1.2, Antigravity, Cursor, Windsurf, Positron.
- Zuban — LSP from the Jedi author, but editor maturity is the weak spot (manual setup, save-only analysis, a rename bug touching stdlib). Posit picked Pyrefly over it for Positron.
Bottom-line positioning
- mypy — safe mature reference whose moat is now the plugin ecosystem alone: deep Django-ORM and pydantic-validator typing the Rust checkers can’t model. (SQLAlchemy 2.0’s
Mapped[]isdataclass_transform-based and portable now — no longer mypy-only.) They match or beat it on everything else, and it stays slowest, LSP-less, and lowest-conformance (58.9%). Default for risk-averse, plugin-dependent teams. - basedpyright — pragmatic power-user pick: pyright’s top-tier engine + the only MIT path to Pylance-grade IDE features in any editor, plus baseline for incremental strict adoption. Single maintainer, single-threaded.
- ty — fastest incremental LSP, cleanest Astral-ecosystem UX (uv/Ruff), now out-conforms mypy. Explicitly beta with “breaking changes between any two versions”; real type-system holes (TypeVarTuple, generic aliases, runtime/recursive protocols), no strict preset, no overload-overlap checks, no native pydantic/django — and by design the quietest checker (its gradual guarantee skips bugs in unannotated code that pyrefly flags). The one to watch.
- pyrefly — most production-proven newcomer (Instagram 20M lines, PyCharm default), aggressive inference that finds bugs in unannotated code, native pydantic/django handling (ORM depth still thin), stable 1.0, Meta-backed.
- Zuban — the most spec-complete of all five (99.3%, the conformance leader) and a true mypy drop-in (>95% of mypy’s test suite, reads existing config), fast. The blockers are non-technical: AGPL-3.0 (needs a paid license for many proprietary codebases), a solo-maintainer bus factor, no framework plugins, a still-rough LSP.
Key sources
- Astral ty blog (beta announce + benchmarks) · ty docs · ty rules reference · ty issues
- basedpyright docs · benefits over pyright
- Pyrefly conformance comparison · Pyrefly FAQ · Pyrefly error kinds · pyrefly issues
- mypy changelog · mypy blog
- Zuban site · docs/features · HN Show HN thread
- python/typing live conformance dashboard · Posit four-checker evaluation (Mar 2026) · sinon.github.io conformance deep-dive · pydevtools comparison