Python Type Hints: Catch Bugs Before Your Users Do
Python ignores type hints when it runs. A checker like mypy doesn't. See one file crash on its first bug, then see mypy find both bugs before anything runs, and learn where to start adding hints in an existing project.
Python won't force you to use type hints. That's exactly why they help. A type hint says what a function expects and what it returns, like price: float. Python itself ignores them when the code runs. A checker like mypy reads them before the code runs and tells you where the types don't match. That's a whole class of bugs found at your desk instead of by a customer.
Skip ahead: If you already write hints and only want to know where to start in an existing project, go to "Adding hints to an existing project".
First, are hints worth it for your code?
It depends on what we are talking about.
- A 30-line script you run once: not really. You'll find the bugs by running it.
- Code other people call, or code you'll maintain for months: yes. The hints are documentation that a tool can check, so they don't go out of date the way comments do.
- A big existing project with no hints: yes, but gradually. Don't stop everything to add hints to every file. That never ends well.
You need Python 3.10 or newer for everything shown here. The str | None style needs 3.10. On older versions you'd write Optional[str] from the typing module.
The syntax
def apply_discount(price: float, percent: float) -> float:
return round(price * (1 - percent / 100), 2)
def find_customer(customers: dict[int, str], customer_id: int) -> str | None:
return customers.get(customer_id)
customers = {7: "Ana Reyes", 12: "Ben Cruz"}
total = apply_discount("1250", 10)
name = find_customer(customers, 31)
print(name.upper())
price: floatmeanspriceshould be a float.-> floatmeans the function returns a float.dict[int, str]is a dictionary withintkeys andstrvalues.str | Nonemeans "a string, orNone".dict.getreturnsNonewhen the key isn't there, so the hint tells the truth.
This file has two bugs. "1250" is text, not a number. And customer 31 doesn't exist, so name is None.
Without a checker: the user finds the bug
Python doesn't check the hints. It just runs the code until something breaks:
python pricing.py
Traceback (most recent call last):
File "/home/you/project/pricing.py", line 11, in <module>
total = apply_discount("1250", 10)
File "/home/you/project/pricing.py", line 2, in apply_discount
return round(price * (1 - percent / 100), 2)
~~~~~~^~~~~~~~~~~~~~~~~~~~~
TypeError: can't multiply sequence by non-int of type 'float'
It crashes on the first bug. The second one, on line 13, is still waiting. You'll only find it after you fix the first, or worse, in production, the first time someone looks up a customer who doesn't exist.
With a checker: you find both, before running anything
Install mypy in your project's virtual environment, then point it at the file:
pip install mypy
mypy pricing.py
pricing.py:11: error: Argument 1 to "apply_discount" has incompatible type "str"; expected "float" [arg-type]
pricing.py:13: error: Item "None" of "str | None" has no attribute "upper" [union-attr]
Found 2 errors in 1 file (checked 1 source file)
Both bugs, with line numbers, and nothing was run. That's the whole value. The output above is from mypy 2.4.0. Other versions word it slightly differently.
The second error is the one I care about most. "This might be None" is the bug that shows up months later, with real data. The checker forces you to decide now what should happen when the customer doesn't exist:
def find_customer(customers: dict[int, str], customer_id: int) -> str | None:
return customers.get(customer_id)
customers = {7: "Ana Reyes", 12: "Ben Cruz"}
name = find_customer(customers, 31)
if name is None:
print("Customer 31 not found")
else:
print(name.upper())
Customer 31 not found
mypy fixed.py
Success: no issues found in 1 source file
After the if name is None check, mypy knows name must be a string in the else branch, so .upper() is fine.
⚠ Warning: Don't "fix" a checker error by adding # type: ignore or changing the hint to Any just to make it quiet. Sometimes that's right, for example with a badly typed library. But most of the time the checker found a real case your code doesn't handle. Silencing it means shipping the bug on purpose.
Adding hints to an existing project
Start where hints pay off most, and stop when they stop paying:
- Functions other people call first. Shared helpers, anything in a
utils.py, the functions used across modules. A wrong call there breaks many places. - Return types next. Especially functions that can return
None. That's where the checker finds the most real bugs. - Run mypy in your normal workflow. Before committing, or in your CI. A checker you run once a year finds nothing useful.
- Leave obvious local variables alone.
count = 0doesn't needcount: int = 0. The checker already knows.
mypy only checks functions that have hints, unless you tell it to be stricter. That's what makes the gradual approach possible. Files without hints don't flood you with errors on day one.
💡 Tip: Use the built-in types directly: list[str], dict[str, int], tuple[int, int]. Old tutorials import List and Dict from typing. That still works, but since Python 3.9 you don't need it.
What hints don't do
- They don't check anything at runtime.
apply_discount("1250", 10)still crashes when it runs. Hints plus a checker catch it before. If the data comes from outside, like a form or a CSV file, you still have to validate it in code. - They don't make Python faster. Normal Python ignores them when running.
- They don't replace tests. A function can have perfect types and still calculate the wrong discount. Types check the shape. Tests check the answer.
Common mistakes
- Writing hints and never running a checker. Then they're just comments that can be wrong.
- Lying in the hint.
-> stron a function that can returnNone. The checker trusts you, so it can't help you. Anyeverywhere.Anyturns checking off for that value.- Trying to hint the whole codebase in one go. Start with the shared functions.
What happens if you skip this
Nothing, for a while. Then the code grows, more people call your functions, and the bugs move from "I see it when I run it" to "a user sees it on Tuesday". Type hints don't make you a better programmer. They just make your mistakes show up earlier, while they're still cheap.
About
Writing type hints, running mypy, fixing the 'might be None' bugs it finds, and adding hints to an existing project gradually. After this, a whole class of bugs shows up at your desk instead of in production.
- Responsible
- Mary Q
- Last Update
- 09/23/2026
- Reading Time
- 6 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.