mypy vs Pyright: Why They Report Different Errors
Run both on the same codebase and Pyright reports far more errors. That is usually not Pyright being stricter, it is mypy skipping unannotated functions by default. Understanding that one difference explains most of the gap.
The usual way teams compare Python type checkers is to run both and count errors. That produces a misleading result, because the two tools disagree about what to check before they disagree about anything else.
mypy skips unannotated functions by default. If a function has no annotations, its body is not type-checked unless you pass --check-untyped-defs or --strict. Pyright checks all code by default, and infers return types from function bodies where mypy assumes Any. On a partially annotated codebase, which is most real codebases, that single difference accounts for the bulk of the error-count gap.
The rest of the differences are about inference quality and ecosystem: how each narrows types, whether literal types survive, and whether you can extend the checker with plugins. Microsoft maintains a public comparison document in the Pyright repository that details these, and it is the primary source for most of what follows.
mypy vs Pyright at a glance
| mypy | Pyright | |
|---|---|---|
| Unannotated functions | Skipped unless --check-untyped-defs or --strict | Checked by default |
| Return type inference | Assumes Any when unannotated | Infers from the function body |
| Type merging | Join to a common supertype | Union of the types |
| Literal types | Widened ("stop" becomes str) | Preserved as Literal["stop"] |
| Plugin system | Yes, including Django and SQLAlchemy | None |
| Language server | No | Yes, powers Pylance in VS Code |
| Written in | Python, compiled with mypyc | TypeScript |
| Config | mypy.ini, setup.cfg, pyproject.toml | pyrightconfig.json, pyproject.toml |
The defaults difference, and how to make a fair comparison
Before concluding that one tool is noisier than the other, put them on equal footing. Most "Pyright found 400 more errors" reports are comparing a checker that reads your whole codebase against one that is skipping large parts of it.
# mypy, checking what Pyright checks by default
mypy --check-untyped-defs src/
# or go all the way
mypy --strict src/
# Pyright at its default (already checks everything)
pyright src/
# Pyright at basic, if the default is too much to adopt at once
pyright --level basic src/Inference differences that produce real false positives
Beyond defaults, the tools infer differently, and Pyright documentation argues its approach yields fewer false positives.
- Joins versus unions. When merging types across branches, mypy joins to a common supertype while Pyright forms a union. Pyright documentation states that joins discard valuable type information and lead to many false positive errors.
- Literal preservation.
(1, "stop")gives PyrightLiteral["stop"]and gives mypystr. If you use literal types for state machines or discriminated unions, mypy widening them costs you the checking you wanted. - Variable declarations. mypy treats a first assignment as an implicit declaration; Pyright infers a union across assignments. mypy behaviour is the more surprising of the two when a variable legitimately holds different types over its life.
Speed: the standard claim is now contested
Pyright documentation states it is 3x to 5x faster than mypy on large codebases, and that has been the received wisdom for years. It is worth treating with more care in 2026.
- That figure predates mypy being distributed as mypyc-compiled binaries and predates the 1.18 optimisation work, which improved mypy substantially on its own codebase.
- Recent independent comparisons find current mypy competitive with, and in some cases faster than, Pyright.
- Both are now far behind the newest entrants. Pyrefly, from Meta, reached stable 1.0 in May 2026 and reports 10-50x over both. ty, from Astral, is still alpha.
Plugins are the one thing Pyright cannot do
This is the clearest either/or in the comparison. mypy supports plugins; Pyright has no plugin system and its maintainers have said it will not get one.
- Django models generate attributes dynamically. Without
django-stubsand its mypy plugin, a type checker cannot follow that, and Pyright cannot load the plugin. - SQLAlchemy declarative models have the same shape of problem.
- If your codebase leans on either, mypy plugin support is a hard requirement rather than a preference, whatever the rest of this comparison says.
Running either one in CI
# mypy
- run: pip install mypy
- run: mypy --strict src/
# Pyright, with machine-readable output for annotations
- run: pip install pyright
- run: pyright --outputjson src/ > pyright.json
# Adopting gradually: fail only on files already clean
- run: mypy src/ --strict --exclude "src/legacy/.*"Can you run both?
Yes, and some large codebases do, though the payoff is narrower than it sounds. Pyright as the editor experience through Pylance and mypy as the CI gate is a common split, because it gives you Pyright inference while you type and mypy plugin coverage where correctness is enforced.
- The cost is maintaining two configurations that must agree, or accepting that your editor and your CI will disagree.
- Disagreements are not hypothetical: the inference differences above mean genuinely different verdicts on the same code.
- For most teams one checker configured properly beats two configured approximately.
Benchmark on your repository before choosing
Build-tool benchmarks published by vendors use repositories chosen to show a difference. Yours is the only one that matters, and both a cold and a warm measurement are needed because CI mostly runs cold.
# cold: no cache, the CI condition
rm -rf node_modules/.cache dist && time <tool> build
# warm: the local development condition
time <tool> build
# and the one people forget: incremental after a one-line change
echo "// touch" >> src/index.ts && time <tool> buildThe verdict
Django or SQLAlchemy codebase: mypy. The plugin ecosystem is not optional for dynamically generated model attributes, and Pyright has no plugin system to load it into.
New codebase, or one you can annotate: Pyright. It checks everything by default, infers return types rather than assuming Any, preserves literal types, and unions rather than joins, which its documentation argues produces fewer false positives. It also gives you the language server that powers Pylance.
Partially annotated legacy codebase: whichever you pick, adopt gradually per directory. Turning on Pyright default checking or mypy --strict across a large untyped codebase produces an error count nobody will action.
And before you compare error counts at all, run mypy --check-untyped-defs so both tools are looking at the same code. Most of the gap people attribute to strictness is that.
Frequently asked questions
Why does Pyright report more errors than mypy?
--check-untyped-defs or --strict, while Pyright checks all code by default and infers return types where mypy assumes Any. Run mypy --check-untyped-defs and the gap narrows sharply.Is Pyright faster than mypy?
Does Pyright support mypy plugins?
django-stubs or SQLAlchemy mypy plugins to understand dynamically generated model attributes, that requirement decides the comparison on its own.