mypy
mypy is a static type checker for Python: it reads the type hints in your code (minutes: int, -> str) and, without running anything, reports every place where the types do not fit together [1].
The quality question it answers: can a value of the wrong type reach this line? It finds a class of bugs that a test finds only if a test happens to pass that value.
Run for this card with mypy 2.4.1, Python 3.12, October 2026.
Install and quick start
poetry add --group dev mypy
poetry run mypy src/
poetry run mypy --strict src/
Reading the output
A lookup module, src/parking/lookup.py, with two type bugs and one untyped copy of the second (constructed example):
RATES = {"standard": 2, "member": 1}
def rate(kind: str) -> int:
return RATES.get(kind)
def label(minutes: int) -> str:
return "Parked " + minutes + " min"
def untyped(minutes):
return "Parked " + minutes + " min"
$ mypy src/
src/parking/lookup.py:5: error: Incompatible return value type (got "int | None", expected "int") [return-value]
src/parking/lookup.py:9: error: Unsupported operand types for + ("str" and "int") [operator]
Found 2 errors in 1 file (checked 4 source files)
(The count of files checked depends on what else is in src/.)
Each line gives the file, the line, the problem and, in brackets, the error code. Line 5: dict.get returns None for an unknown key, and the function promises an int. Line 9: adding a string and a number raises TypeError at run time.
Common mistakes
- Expecting mypy to check untyped code. Line 13 has the same bug as line 9, but mypy said nothing: by default it does not check the body of a function without annotations.
--strictreported the function on line 12 asFunction is missing a type annotation [no-untyped-def], also flagged the untyped receipt function in another file, and addedno-any-returnon line 9: 5 errors in 2 files. - Silencing with
Anyor# type: ignore. Both switch the check off. Write# type: ignore[return-value]with the code, so the next reader knows what was accepted. - Hiding the
Nonecase. The fix for line 5 is a decision, not a cast: raise for an unknown kind, return a default (RATES.get(kind, 2)), or declare-> int | Noneand make the callers handle it. - Turning on
--strictfor an old code base at once. Hundreds of errors get ignored. Start with the default, add--strictper module, and keep the count falling.
In CI
- run: poetry run mypy src/
mypy exits 1 when it reports an error.
Go deeper
- Pattern, Type and Taint Checks: what a type check can prove, and what it cannot.
- Soundness and Completeness: why a checker either misses bugs or reports false alarms.
References
- The mypy project,“mypy Documentation.” 2026. [Online]. Available: https://mypy.readthedocs.io/
Disclaimer: AI is used for text summarization, polishing and explaining. Authors have verified all facts and claims. In case of an error, feel free to file an issue.