How do Python type checkers compare?
mypy, Pyright, ty, Pyrefly, Basedpyright, and Zuban check Python types before runtime. They differ in which diagnostics run by default, how much they infer without annotations, and which settings make CI fail. Pair a type checker with Ruff for linting and formatting.
The handbook recommends Pyrefly for new projects because it combines a language server, configuration migration, and annotation-generation tools. Initialize its configuration before relying on a successful check. Keep an established checker when its plugins or diagnostic policy already meet the project’s needs.
Compare the default checking policies
| Checker | Unannotated function bodies | Unannotated function returns | Stricter checking |
|---|---|---|---|
| mypy | Skipped | Any |
--check-untyped-defs checks bodies; --strict also requires annotations |
| Pyright CLI | Checked in standard mode |
Inferred | typeCheckingMode = "strict" |
| Basedpyright | Checked in recommended mode |
Inferred | strict and all modes; recommended already enables additional diagnostics |
| ty | Checked | Unknown by default |
Individual rules and analysis options; no --strict preset |
| Pyrefly | Skipped under unconfigured basic; checked under default |
Unknown under basic; inferred under default |
preset = "strict" |
| Zuban | Checked in zuban check; skipped in zuban mypy |
Inferred in check mode; Any in mypy mode |
Mode-specific configuration and strictness settings |
These are CLI defaults. Pylance defaults to off for type checking, even though the standalone Pyright CLI defaults to standard. Pyrefly can also translate an existing checker configuration instead of using its unconfigured fallback. See the Pyright configuration, Basedpyright defaults, and Pyrefly configuration for the selection rules.
Checking a body does not imply inferring types for every parameter. An unannotated parameter can remain Any or Unknown even when a checker catches an invalid expression elsewhere in that function.
Reproduce a difference in unannotated code
In a disposable uv project, install the six checkers:
uv add --dev mypy pyright basedpyright ty pyrefly zubanuv records the dependencies and resolves their versions in uv.lock. The --dev flag marks them as development dependenciesA package needed during development (testing, linting, formatting) that is not shipped to users of your project. Installed with the --dev flag.
, keeping them out of production installs.
Save this file as body.py:
def broken():
return 1 + "x"Run each command independently from the project root. uv run executes the command inside the project’s virtual environment, ensuring all dependencies are installed first.
| Command | Expected result without checker configuration |
|---|---|
uv run mypy body.py |
No error; exit 0 |
uv run mypy --check-untyped-defs body.py |
Operator error; exit 1 |
uv run pyright body.py |
Operator error; exit 1 |
uv run basedpyright body.py |
Operator error and additional diagnostics; exit 1 |
uv run ty check body.py |
unsupported-operator; exit 1 |
uv run pyrefly check body.py |
No error under basic; exit 0 |
uv run pyrefly check --preset default body.py |
unsupported-operation; exit 1 |
uv run zuban check body.py |
Operator error; exit 1 |
uv run zuban mypy body.py |
No error; exit 0 |
This is a diagnostic example, not a coverage score. A green run can mean the relevant check is disabled. How mypy, ty, and Pyrefly handle untyped code separates body checking from return inference.
Compare container inference
Save this as containers.py and run the same checker commands with that filename:
from typing import reveal_type
values = []
values.append(1)
reveal_type(values)
values.append("text")mypy, configured Pyrefly under default, and Zuban infer list[int] and reject the string append. Pyright and Basedpyright reveal list[Unknown]; Basedpyright’s recommended mode also reports the unknown member types. ty permits both appends and infers a union of the element types. An explicit values: list[int] = [] makes the intended constraint clear to every checker.
Unknown permits operations like Any, but some checkers diagnose its presence separately. Treating them as interchangeable hides a useful part of Basedpyright’s diagnostic policy.
Match editor and CLI configurations
Pyright includes an open-source language server. Microsoft’s proprietary Pylance extension uses Pyright by default and also supports Pyrefly as a diagnostics backend through the Type Server Protocol. Choose Pylance + Pyrefly in the Diagnostics Source setting to keep Pylance’s editor features while using Pyrefly’s analysis. Both Microsoft’s integration instructions and Pyrefly’s instructions describe the available setup.
Pyrefly also has a standalone extension, which its team recommends for most users. The Pylance integration does not change Pylance’s license or make it available to VS Code forks. Basedpyright, ty, and Pyrefly provide language servers that work in other LSP-capable editors. Zuban exposes a language server through zuban server. mypy editor integrations run its checker or daemon rather than a built-in language server.
Use the same executable version, Python environment, file selection, and diagnostic settings in the editor and CI. Sharing an analysis engine alone does not guarantee identical diagnostics: editors can check only open files or choose configurations per source file.
Find the configuration that actually applies
| Checker | Project configuration | Discovery detail |
|---|---|---|
| mypy | mypy.ini, .mypy.ini, pyproject.toml, setup.cfg |
Uses the first applicable config; does not merge files; TOML table is [tool.mypy] |
| Pyright | pyrightconfig.json, [tool.pyright] |
JSON takes precedence over TOML |
| Basedpyright | pyrightconfig.json, [tool.basedpyright] |
Supports [tool.pyright] as a fallback; do not define both TOML sections |
| ty | ty.toml, [tool.ty] |
ty.toml wins in the same directory; project settings merge with user settings |
| Pyrefly | pyrefly.toml, [tool.pyrefly] |
Nearest applicable config; supports layered settings; unconfigured fallback differs from configured defaults |
| Zuban | Mypy config in mypy mode; [tool.zuban] in check mode |
Mode changes semantics and option names; do not assume every mypy setting is supported |
Run from the project root and inspect the full output for rejected settings. A configuration file for one checker does not automatically configure another. Explicit paths can also change file selection or configuration discovery; check each tool’s reference before copying CI commands into a monorepo.
Preserve suppressions and CI failures deliberately
| Checker | Targeted suppression | Default diagnostic failure policy |
|---|---|---|
| mypy | # type: ignore[arg-type] |
Errors fail; notes do not |
| Pyright | # pyright: ignore[reportArgumentType] |
Errors fail; warnings need --warnings to fail |
| Basedpyright | # pyright: ignore[reportArgumentType] |
Errors and warnings fail in recommended mode |
| ty | # ty: ignore[invalid-argument-type] |
Errors and warnings fail; --exit-zero-on-warning makes warnings advisory |
| Pyrefly | # pyrefly: ignore[bad-argument-type] |
Errors fail; --min-severity warn includes warnings in the failure threshold |
| Zuban | # type: ignore[arg-type] |
Type errors fail |
These examples suppress the wrong argument in a typed call; rule names differ between checkers. A mypy-coded # type: ignore[assignment] does not suppress ty’s invalid-assignment. Basedpyright disables standard type: ignore support by default, while Pyrefly can accept other checkers’ ignores through enabled-ignores or permissive-ignores. Audit suppressions during migration rather than copying them unchanged.
For required checks, retain a nonzero exit status in CI. Invalid options, invalid configuration, and internal failures may use codes other than 1. Do not turn every failure into success with || true, continue-on-error, or an unconditional exit-zero flag. The ty exit-code reference distinguishes diagnostics from invocation and internal errors.
Evaluate speed and conformance as measurements
A useful benchmark records tool versions, source revision, configuration, selected files, Python environment, hardware, cache state, worker counts, and the commands used. Check that each tool actually analyzes the intended files and reports a seeded error before timing it. A checker that skips the relevant analysis can appear fast while answering a different question.
Measure a representative project with the diagnostic policy that CI will use. A result from a different configuration or source revision does not establish a universal speed ranking.
The Python typing conformance suite tests agreement with typing rules. Fully passing test counts and weighted scores that award partial credit are different metrics. A reproducible comparison needs the suite revision, checker versions, runner commands, and chosen metric. Conformance results do not measure framework support or how many bugs a checker finds in an application.
Choose a checker for the project’s constraints
- Pyrefly: the handbook’s default for new projects; initialize configuration and review its migration output. Its language server, inference tools, and Pylance option support both editor and CLI use.
- mypy: retain it for mypy plugins, established per-module policies, or existing CI coverage. A migration should preserve those guarantees before replacing the job.
- Pyright: use its inference and configurable diagnostics when they match the project. Its CLI is open source and can run independently of Pylance.
- Basedpyright: use its additional diagnostics and open-source editor features, including in VS Code forks. Budget time to tune its stricter defaults.
- ty: use its language server and configurable rule policy alongside Astral’s uv and Ruff. Check unannotated-body diagnostics separately from return inference, which is opt-in.
- Zuban: evaluate its mypy mode against the project’s existing diagnostics and plugins, or use its check mode for more inference. Its AGPL-3.0/commercial licensing differs from the other checkers’ MIT licenses; review the project’s licensing terms.
Pick your next step
- Migrate from mypy to Pyrefly to evaluate the handbook’s default.
- Migrate from mypy to ty or from Pyright to ty while comparing diagnostics.
- Gradually adopt type checking in an existing project.
- Test a library against multiple checkers when supporting downstream typing consumers.