Hermes Wiki
Developer/Languages/Python/TypingAndStaticAnalysis/Fundamentals/mypy-vs-pyright-static-type-checking-in-python

mypy vs. Pyright: Choosing a Python Static Type Checker

Concept

Python's type hints (PEP 484 and successors) are pure annotations — the interpreter never checks them at runtime. All the enforcement value comes from a separate static type checker run in CI or an editor, and the two dominant ones, mypy and Pyright, take meaningfully different design stances rather than being interchangeable implementations of the same spec.

mypy is the original, reference-adjacent Python type checker, written in Python itself, maintained under the same typing community that also stewards the typing PEPs. It's permissive by default — a fresh mypy run on an untyped codebase reports relatively little until specific strictness flags (--strict, --disallow-untyped-defs, etc.) are turned on individually, which makes it well suited to incrementally typing an existing large codebase without a wall of day-one errors.

Pyright is Microsoft's checker, written in TypeScript, and is the engine behind Pylance — the default Python language server in VS Code. It's strict by default, checks more of the typing spec out of the box with no flags required, and — because it's written in TypeScript rather than Python — runs meaningfully faster on large codebases (commonly cited at roughly a third to a fifth of mypy's wall-clock time on the same code). Independent typing-conformance benchmarks put Pyright's spec conformance well ahead of mypy's out-of-the-box conformance (mid-90s% vs. roughly 60%), though that gap narrows once mypy is run with its strict flags enabled rather than defaults.

Both consume the same typing module and the same annotation syntax — a codebase isn't locked into one or the other by how it's typed, only by which checker's edge-case interpretations and plugin ecosystem it's built around.

Tradeoffs

Checker Strengths Cost
mypy Permissive-by-default eases incremental adoption on legacy code; large plugin ecosystem (Django, SQLAlchemy, Pydantic-adjacent integrations); Python-native, easiest to extend/patch for a Python team Slower on large codebases; lower out-of-the-box spec conformance until strict flags are explicitly enabled — "clean mypy run" can hide real type errors a stricter checker would catch
Pyright Strict, spec-conformant by default; fast (TypeScript implementation); the same engine as VS Code's Pylance, so CLI results match editor red squiggles exactly Requires Node.js to run as a CLI/CI tool in a Python project — an extra runtime dependency for a Python-only stack; smaller plugin ecosystem than mypy's

Neither is uniformly "correct" — a codebase that's already mid-migration from untyped to typed genuinely benefits from mypy's incremental, flag-gated strictness, while a greenfield project benefits from Pyright's stricter defaults catching mistakes from day one rather than requiring the team to remember to turn strictness on later.

When to use / when not to

  • Reach for Pyright as the default on a new project, or any codebase already committed to VS Code + Pylance as the primary editor experience — CLI and editor then agree exactly, since they share the same engine, and strict-by-default catches more real bugs without configuration effort.
  • Reach for mypy specifically when incrementally typing a large existing codebase that started untyped — its permissive default and fine-grained flags (module-by-module --strict opt-in via config) let a team ratchet up strictness gradually instead of facing thousands of errors on day one, and its plugin ecosystem matters if the stack leans on Django/SQLAlchemy-style dynamic patterns that need typing help.
  • Don't assume a "passing" mypy run with default settings means the codebase is well-typed — check what strictness flags are actually enabled before trusting a green run as a real signal; a permissive default config can pass while missing errors Pyright would flag immediately.
  • Don't treat the choice as permanent or exclusive — some teams run both (mypy in CI for its plugin coverage, Pyright/Pylance in-editor for fast feedback), accepting the minor risk of the two disagreeing on an edge case in exchange for editor speed plus CI plugin support.
  • If evaluating a switch on cost/speed alone, note that CI minutes on a large codebase are a genuine expense — Pyright's speed advantage is not merely a developer-experience nicety at scale.

Common pitfall

Adopting a type checker with its default (permissive) configuration and treating a clean run as proof the codebase is soundly typed. mypy's out-of-the-box behavior allows untyped function bodies, implicit Any in several positions, and other gaps that a --strict run — or Pyright's default — would flag; a team that never revisits the initial lenient config accumulates a false sense of type safety that a real refactor or a subtler bug (a None reaching code that assumed a non-optional value) can slip straight through. The fix is deciding strictness deliberately — pick a default level, document it, and ratchet it up over time — rather than leaving whatever the tool shipped with unexamined.

Engineering Lens

The mypy-vs-Pyright decision is really a proxy for a bigger question: how much of "types are documentation" vs. "types are an enforced contract" does this codebase actually want? A team that treats types as living documentation, added gradually as code gets touched, gets real value from mypy's forgiving defaults and incremental-adoption flags. A team that wants types to function as an enforced contract from day one — closer to what a statically-typed language gives for free — gets more value from a strict-by-default checker catching violations immediately rather than requiring someone to remember to turn strictness on. Neither answer is wrong, but picking a checker without first deciding which of those two the team actually wants produces a false sense of safety either way: a lenient checker configured loosely looks the same in CI as a strict one, right up until a real type error slips through in production.

Sources

Hermes Wiki