Type Hints — Optional, Union, Generics & mypy
IntermediateType hints document intent and let tools (mypy, IDEs, FastAPI, Pydantic) catch bugs before runtime — Python stays dynamic, the annotations stay optional but professional.
Overview
Since Python 3.5, you can annotate parameters, returns and variables with types. The interpreter IGNORES them at runtime — they exist for humans, IDEs, static checkers (mypy/pyright), and frameworks that read them (FastAPI builds validation and docs from your annotations; Pydantic builds parsing). Modern syntax (3.10+) is clean: list[int] not List[int], int | None not Optional[int]. In hiring terms: typed Python is what production codebases look like — writing it fluently marks you as industry-ready.
The Syntax You Will Actually Use
Annotate function signatures first — highest value per keystroke. X | None must be handled before use (mypy enforces it), eliminating a whole class of NoneType errors.
def average(marks: list[int]) -> float:
return sum(marks) / len(marks)
def find_student(roll: int) -> dict[str, str] | None: # may be absent
db = {1: {"name": "Asha"}}
return db.get(roll)
s = find_student(2)
# print(s["name"]) # mypy error: s might be None!
if s is not None:
print(s["name"]) # narrowed — mypy happy
# Collections & defaults
def top_k(scores: dict[str, int], k: int = 3) -> list[tuple[str, int]]:
return sorted(scores.items(), key=lambda kv: -kv[1])[:k]
# Variables & aliases
Matrix = list[list[int]] # type alias
grid: Matrix = [[1, 2], [3, 4]]
from typing import Callable
def apply_twice(f: Callable[[int], int], x: int) -> int:
return f(f(x))
# Run the checker: pip install mypy && mypy app/Generics & TypeVar — Preserve the Type Through
A TypeVar says "same type in as out". Generic containers parameterize classes. This is how library authors keep your IDE smart end-to-end.
from typing import TypeVar, Generic
T = TypeVar("T")
def first(items: list[T]) -> T | None: # returns element type!
return items[0] if items else None
n = first([1, 2, 3]) # mypy knows: int | None
s = first(["a", "b"]) # mypy knows: str | None
class Stack(Generic[T]):
def __init__(self) -> None:
self._data: list[T] = []
def push(self, item: T) -> None:
self._data.append(item)
def pop(self) -> T:
return self._data.pop()
st: Stack[int] = Stack()
st.push(10)
# st.push("hi") # mypy error: expected int
# Why this matters beyond style — FastAPI reads your hints:
# @app.get("/users/{user_id}")
# def get_user(user_id: int) -> UserOut: ...
# ^ validation, conversion AND OpenAPI docs, all from typesKey Points to Remember
- 1Hints are ignored at runtime — enforced by mypy/pyright, exploited by FastAPI/Pydantic
- 2Modern spelling: list[int], dict[str, int], int | None (3.10+)
- 3X | None forces callers to handle absence — the end of surprise NoneType errors
- 4TypeVar preserves types through functions; Generic parameterizes classes
Interview Questions
Sign in to ask AriaDo type hints affect Python runtime behaviour? Who consumes them?
What does Optional[int] mean and how does mypy force you to handle it?
Why does FastAPI care about your type annotations?
Ask Aria about Type Hints — Optional, Union, Generics & mypy
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.