Type Hints
Intermediate · 18 min read · ▶ live playground · ✦ checkpoint
Python will let you pass "forty-two" to a parameter you meant to be a number. Type hints give you a way to write that expectation directly into the function, so humans and tools can spot the mismatch before it becomes a bug.
Annotate the function signature
A type hint is a label in your code that says what kind of value you expect. An annotation is the syntax Python uses for that label.
In a function, a parameter annotation goes after a parameter name with :, and a return annotation goes after -> before the final colon. The whole def line is the function signature, which means the part that shows the function name, parameters, and returned shape.
def score_label(player: str, score: int) -> str:
return f"{player}: {score}"
print(score_label("Mina", 42))
print(score_label("Ravi", "thirty-five"))
print(score_label.__annotations__)Mina: 42Ravi: thirty-five{'player': <class 'str'>, 'score': <class 'int'>, 'return': <class 'str'>}
Read the first line as: player should be a str, score should be an int, and the function should return a str.
The __annotations__ dictionary shows that Python stores the hints as metadata. Metadata is information about the code, not the main data your program is processing.
Hints do not enforce types at runtime
Runtime is when your program is actually running. Python does not enforce type hints at runtime. The second call above passed a string where the hint said int, and Python still ran the function because the function body could format that value into text.
That does not make hints fake. It means they have a different job.
def add_bonus(score: int, bonus: int = 10) -> int:
return score + bonus
print(add_bonus(40))
print(add_bonus("40", " points"))5040 points
add_bonus("40", " points") works because + can join two strings. The hints did not convert "40" into 40, and they did not stop the call.
Type hints are labels on the recipe card from 7.1. They say "this blank expects an integer" or "this recipe returns text." They are not a locked door. Python still runs the instructions you wrote.
So why write them?
- They help you read a function without scanning the whole body.
- They help editors suggest methods and catch suspicious calls.
- They make function expectations visible while you read.
- They give future you a faster way back into the code.
Hint common value shapes
Use the same type names you already know: int for integers, str for strings, bool for booleans, and float for floating-point numbers. For a list, put the item type in square brackets: list[int] means "a list of integers."
def total_points(scores: list[int]) -> int:
total = 0
for score in scores:
total = total + score
return total
def shout_names(players: list[str]) -> list[str]:
shouted = []
for player in players:
shouted.append(player.upper())
return shouted
print(total_points([10, 20, 5]))
print(shout_names(["Mina", "Ravi"]))35['MINA', 'RAVI']
list[int] does not mean "any list." It means every item should be an integer. list[str] means every item should be a string.
You can add hints gradually. A small script might not need them everywhere. A function that other code calls often is a better place to start, because its signature is a promise many readers depend on.
Use Optional for None
Sometimes a function returns a real value when it finds one, and None when it does not. Optional[int] means "an int or None."
The from typing import Optional line imports a standard-library name. An import brings a name from another Python module, an importable bundle of reusable names and tools, into your file; modules get a full lesson in 8.1.
from typing import Optional
def first_passing_score(scores: list[int]) -> Optional[int]:
for score in scores:
if score >= 40:
return score
return None
print(first_passing_score([35, 42, 48]))
print(first_passing_score([12, 29]))42None
Optional[int] does not mean the argument is optional. It means the value might be missing, and Python represents that missing value with None, the reserved empty seat from lesson 2.2.
You may also see int | None in modern Python code. It means the same idea. This lesson uses Optional[int] because many docs and codebases still use it, and the name makes the beginner meaning visible.
Try it - make the contract visible
Run this roster formatter, then change the data and the fallback. The code works like ordinary Python, but the hints make each function's promise visible before you read the body.
This example also shows dict[str, int]: a dictionary whose keys should be strings and whose values should be integers. You do not need to memorize every collection hint now. The pattern is enough: outer shape first, item shapes inside brackets.
Read hints as contracts
A contract is an expectation two pieces of code agree on. Type hints are contracts for readers and tools. They tell you what should go in and what should come out.
They do not replace tests. They do not replace clear names. They do not make Python a different language. They add one more layer of communication right where a function begins.
For now, aim for this habit:
- Annotate parameters on functions that other functions call.
- Annotate return values when the result is not obvious.
- Use
Optional[...]wheneverNoneis a normal possible result. - Keep the body honest: if the hint says
-> str, return text on every path.
Checkpoint
Answer all three to mark this lesson complete
You now have the whole beginner function toolkit: define, call, pass, return, scope, higher-order behavior, and readable type contracts. Next, the course opens the door to modules, where you split code across files and use Python's standard library on purpose.