Static Type Checking with mypy
Reviewed & published by Brayan K
mypy reads your type hints and checks your code without running it, catching whole classes of bugs before they reach production. Python stays dynamic at runtime, but mypy gives you the safety net of a typed language during development.
Learn Static Type Checking with mypy in our free Python course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • How to run mypy and read its output
- • Gradual typing: adding annotations incrementally
- • Optional , Union , and list[int]
- • Function annotations and reveal_type
- • # type: ignore , Any , and strict mode
- • Generics with TypeVar and Protocol
- • How mypy catches bugs before runtime
🔎 1. What mypy Does
Run it from the command line on a file or whole project:
🐛 2. Catching a Bug Before Runtime
With annotations, mypy spots a mismatch your program would otherwise crash on:
Plain Python would only fail when this line executes; mypy flags it immediately.
🟢 Worked Example: watch the bug happen, then watch mypy catch it
Here is the single most common bug mypy saves you from: a function that can return None, handed straight to a function that cannot cope with None. Run it — the first line prints happily and the second one blows up.
Now save that same file as shop.py and run mypy shop.py on your own machine. It never executes a line, yet it reports:
Read that carefully, because it teaches you how mypy thinks. It flags both lines — including the apple one that works perfectly today. mypy reasons about types, not values: it cannot know "apple" is in the dictionary, only that find_price is allowed to return None. Any call that could hand None to an int parameter is a bug waiting for the right input.
The fix is not to delete the annotation. It is to handle the None case, which also makes the program better:
Run mypy on that version and you get the line every Python developer wants to see:
Narrowing is the word for what the if did: inside a branch where None has been ruled out, mypy quietly upgrades the type from int | None to int. You get this for free from if x is None , isinstance() checks, and early returns.
🌱 3. Gradual Typing
🧰 4. Common Type Annotations
Annotation
Means
list[int]
A list whose items are ints
Optional[int]
int or None (int | None)
Union[int, str]
Either an int or a str (int | str)
🎯 Your Turn: annotate two functions
The bodies are written; only the annotations are missing. Fill in the four blanks using the modern pipe syntax (str | None), not Optional[str] — the self-check at the bottom prints the annotations back to you, and the two forms print differently.
Remember that annotations do not change what your program does — Python records them and carries on. They exist so mypy (and the next human) can check your intentions. That is why the self-check above can print them straight back at you.
🔬 5. reveal_type
Ask mypy what type it inferred for an expression:
🤫 6. type: ignore, Any, and Strict Mode
🧬 7. Generics and Protocols
Use TypeVar for generic functions that preserve types:
Use Protocol for structural typing — "if it has these methods, it fits":
🏁 Mini-Challenge: type a small text module
No blanks and no bodies this time — just a brief. Write three functions, fully annotated (every parameter and every return type), using the pipe syntax for the one that can return nothing:
- word_count(text) — how many whitespace-separated words are in a string
- longest(words) — the longest word in a list, or None when the list is empty
- tally(words) — a dict mapping each word to how many times it appears
When it runs clean, save it on your own machine and finish the job with mypy --strict yourfile.py . Strict mode is the setting that refuses to let an unannotated function slip through, so it is the honest test of whether you annotated everything.
🎉 Conclusion
✔ Express intent with Optional, Union, and generics
Type hints turn Python into a safer language without giving up its flexibility.
📋 Quick Reference — mypy
Syntax
What it does
mypy app.py
Type-check a file
int or None
reveal_type(x)
Report inferred type
# type: ignore
Silence one line
mypy --strict .
Strictest checking
You can now use mypy to catch type bugs early and keep large Python codebases reliable.
Congratulations! You've reached the end of the advanced Python track.
Practice quiz
What kind of tool is mypy?
- A static type checker
- A code formatter
- A test runner
- A runtime debugger
Answer: A static type checker. mypy analyzes your annotated code without running it, reporting type errors statically before runtime.
How do you run mypy on a file called app.py?
- pip check app.py
- mypy --run app.py
- mypy app.py
- python app.py --types
Answer: mypy app.py. You invoke the checker directly: 'mypy app.py' (or 'mypy .' for a whole project).
What does 'gradual typing' mean?
- You must type everything at once
- You can add type hints incrementally, mixing typed and untyped code
- Types are inferred at runtime
- Types are checked slowly
Answer: You can add type hints incrementally, mixing typed and untyped code. Gradual typing lets you annotate code piece by piece; unannotated parts are treated as dynamic.
Which annotation means 'an int or None'?
- int & None
Continue this course
- Previous: Building APIs with FastAPI