Skip to content

mypy 2.0 picks parallelism over a rewrite

May 9, 2026·Tim Hopper

For most of the past year, the speed conversation around Python type checkers has had one shape: mypy is the slow incumbent, and the fast alternatives (ty, Pyrefly, Zuban) earn their speed by being written in Rust. mypy 2.0, released May 6, 2026, takes a different route. It runs across multiple cores.

Run mypy across cores

The headline feature is --num-workers N (or -nN). Pass a worker count and mypy splits type checking across processes:

uv run mypy -n 8 src/

The release notes report up to 5x faster checking with 8 workers, with the disclaimer that “the speedup depends on the import structure of your codebase and your environment.” Parallel mode is marked experimental and implicitly enables mypy’s native parser (the Ruff-based parser introduced in 1.18). The release notes acknowledge “minor semantic differences between parallel and non-parallel modes” that future releases will close, so single-process mypy remains the conservative choice for CI gates that cannot tolerate flake.

The work has been in flight for years. Issue #933 (“Faster, parallel type checking”) has been open since 2015. The implementation that landed in 2.0 came together across #21119, #21324, and #21387 earlier this year.

Account for the breaking defaults

Three flag flips matter on upgrade because they can produce errors in code that was passing under mypy 1.x:

  • --local-partial-types is now default. This changes inference for variables that the same name is assigned to in different scopes.
  • --strict-bytes is now default per PEP 688. Passing a bytearray or memoryview where bytes is expected no longer type-checks.
  • --allow-redefinition now behaves like the prior --allow-redefinition-new, requires --local-partial-types (which is fine, since that is now default), and forbids two type annotations on the same variable. Pass --allow-redefinition-old to keep the legacy behavior.

The release also rejects --python-version 3.9 outright. Targeting Python 3.9 was already deprecated; 2.0 promotes that deprecation to a hard error. The minimum target is now 3.10. mypy 1.20 had separately dropped runtime support for Python 3.9 itself.

A smaller cleanup worth knowing: --ignore-missing-imports is now respected uniformly. Previous versions special-cased a small set of bundled stubs, which produced surprising behavior when those packages got real stubs elsewhere.

Read the speed gap honestly

Parallel checking does not turn mypy into ty. The handbook’s type checker comparison benchmarks single-threaded mypy 1.20 at 0.88s on Rich and 2.39s on SQLGlot. Even at the 5x best case, parallel mypy on those projects still trails the Rust-based checkers on small codebases, where worker startup overhead eats much of the savings. The release explicitly frames the 5x figure for large projects.

What changes is the framing of the trade-off. The “Rust is the only path to fast Python type checking” claim is no longer the only argument worth taking seriously. mypy still has the broadest stub coverage and the most mature plugin ecosystem (Django, Pydantic, and SQLAlchemy all ship mypy plugins). For projects that already depend on those things, multi-core mypy may be enough to defer a migration.

For teams that have already moved or are mid-migration (FastAPI’s incremental adoption of ty is a useful template), 2.0 is mostly an upgrade-and-test exercise. Read the breaking-defaults list, run uv run mypy -n 8 src/ on a clean checkout, and see what falls out.

Learn more

Last updated on