website/docs/compare.mdx
{/*
This page sets out how mypy, Pyright, and Pyrefly differ, so you can judge whether moving to Pyrefly is worth it for your project. Every codebase has its own constraints, and which tool you should choose depends on what you find works best for your specific needs.
Where the sections below cite numbers, they come from the benchmarks and dashboards linked under References. All three tools make changes over time, so treat any figure as a snapshot and rerun it if the decision hinges on it.
| Question | mypy | Pyright | Pyrefly |
|---|---|---|---|
| What is it written in? | Python | TypeScript | Rust |
| How fast is it? (benchmarks) | Slow (18.3s for pandas, 36.3s for pytorch) | Faster (8.8s for pandas, 16.7s for pytorch) | Fastest (1.5s for pandas, 2.1s for pytorch) |
| Can it be used as a language server? | No | Yes | Yes |
| How closely does it follow the typing spec? (dashboard) | 108.5/145 conformance tests (74.8%) | 135.5/145 (93.4%) | 140.5/145 (96.9%) |
| How is it configured? | mypy.ini, setup.cfg, or [tool.mypy] | pyrightconfig.json or [tool.pyright] | pyrefly.toml or [tool.pyrefly] |
| Can it infer types for code that has no annotations? (details) | Some inference: empty containers | Some inference: return types | Advanced inference: empty containers, parameters, and return types |
| Can it automatically add type annotations to my codebase? | No | No | Yes, with pyrefly infer |
| Pydantic support | Plugin | Partial support via dataclass_transform | Built-in |
| Django support | Plugin | Partial support via stubs | Built-in |
| attrs support | Built-in | Partial support via dataclass_transform | Built-in |
Pyrefly is not a reimplementation of either mypy nor Pyright and will not produce identical diagnostics. A successful migration ends in understood and accepted differences rather than in matching output.
Different type checkers have a different interpretation of what "strictness" means, and what the default strictness should be. How different checkers handle empty containers is a clear example of this, given x = [] followed by x.append(1), mypy and
Pyrefly infer the element type from that first use and flag a later
x.append("two"), while Pyright infers list[Any] and reports nothing.
Pyrefly's behavior here can be adjusted using the
infer-with-first-use config.
All three tools also have a setting called strict, and the three are not equivalent.
Each enables a different set of checks:
| Checker | Setting | What it enables |
|---|---|---|
| mypy | strict = true | A bundle of optional checks. Which checks the bundle contains changes between releases. |
| Pyright | typeCheckingMode = "strict" | Pyright's strict rule defaults, which can be overridden per rule. |
| Pyrefly | preset = "strict" | Pyrefly's strict error kinds and behavior settings. Any setting you specify overrides the preset. |
Because the bundles differ, code that passes one tool's strict mode will not
necessarily pass another's. When comparing configurations, it is more reliable
to look at the specific policies you care about, such as implicit Any, missing
annotations, override decorators, and unused ignores, than to match the strict
settings to each other. mypy strict mode and
Pyright strict mode cover what each one turns
on and their closest Pyrefly equivalents.
# uv
uv add --dev pyrefly
# pip
python -m pip install pyrefly
To see what Pyrefly reports on your code without changing anything in the project:
pyrefly check
That works with no Pyrefly config. If Pyrefly finds an existing mypy or Pyright configuration and no Pyrefly config governs the files, it reads that configuration for the run without writing anything to disk.
Once you decide to go ahead, run pyrefly init to convert that configuration
into a native one you can commit. Check out
Migrate from mypy and
Migrate from Pyright for a more detailed
walkthrough.
If a specific feature, diagnostic, plugin, or configuration option is what stops you from adopting Pyrefly, please open an issue on GitHub. Gaps that block real migrations are the ones we prioritize.
python/typing repository.x = [] and the
trade-offs of each.