Skip to content
Latchkey

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

mypyPyright
Unannotated functionsSkipped unless --check-untyped-defs or --strictChecked by default
Return type inferenceAssumes Any when unannotatedInfers from the function body
Type mergingJoin to a common supertypeUnion of the types
Literal typesWidened ("stop" becomes str)Preserved as Literal["stop"]
Plugin systemYes, including Django and SQLAlchemyNone
Language serverNoYes, powers Pylance in VS Code
Written inPython, compiled with mypycTypeScript
Configmypy.ini, setup.cfg, pyproject.tomlpyrightconfig.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.

Comparing like for like
# 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 Pyright Literal["stop"] and gives mypy str. 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-stubs and 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

.github/workflows/ci.yml
# 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.

Terminal
# 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> build

The 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?
Mostly because of defaults, not strictness. mypy skips unannotated functions unless you pass --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?
Pyright documentation says 3x to 5x faster on large codebases, but that figure predates mypy mypyc-compiled builds and its 1.18 optimisation work, and recent independent comparisons find current mypy competitive. Benchmark on your own repository rather than relying on either claim.
Does Pyright support mypy plugins?
No. Pyright has no plugin system at all. If you depend on django-stubs or SQLAlchemy mypy plugins to understand dynamically generated model attributes, that requirement decides the comparison on its own.
What does Pyright strict mode actually change?
It enables roughly 30 additional rules on top of a default that already checks everything, including requiring annotations on all function parameters and return values. Teams commonly see around a 10x increase in reported errors, so enable it per-directory rather than repository-wide.
Should I use mypy or Pyright for a new project?
Pyright, unless you are on Django or SQLAlchemy. It checks everything by default, has better inference behaviour on unions and literal types, and provides the language server behind Pylance so your editor and your checker agree.
What are ty and Pyrefly?
Newer Python type checkers built for speed. Pyrefly, from Meta, reached stable 1.0 in May 2026 and reports 10-50x faster than mypy and Pyright on large codebases. ty, from Astral, was still alpha as of mid-2026. Both are worth watching; neither yet has the ecosystem maturity of mypy or Pyright.
Can I use Pyright in the editor and mypy in CI?
Yes, and it is a common split: Pyright inference while you type via Pylance, mypy plugin coverage as the enforced gate. The cost is two configurations that must be kept in agreement, and the inference differences mean they can genuinely disagree about the same code.
How do I stop type-check failures on code nobody changed?
Pin the checker version in CI. Both tools add rules and sharpen inference in minor releases, so an unpinned checker means an unrelated dependency update can turn a build red. Pin it, and upgrade deliberately as its own pull request.

Related guides

References

Run this faster and cheaper on Latchkey managed runners - self-healing included. Start free → 30-day trial · No credit card