Type Hints
What type hints are
Section titled “What type hints are”A type hint is an annotation attached to a variable, function parameter, or return value that declares the expected type.
Python stores these annotations in __annotations__ but never acts on them at runtime — they are metadata for static analysis tools and human readers.
x: int = 10name: str = "Alice"The : int and : str have zero effect on execution. They exist solely to communicate intent.
Annotating function signatures
Section titled “Annotating function signatures”The most valuable place for type hints is function signatures — they document the contract of every callable.
def add(a: int, b: int) -> int: return a + b
def repeat(text: str, times: int = 1) -> str: return text * times-> int annotates the return type. Default values come after the type annotation: times: int = 1.
Built-in generic types
Section titled “Built-in generic types”Python 3.9+ lets you use the built-in collection types directly as generics — no import needed.
scores: list[int] = [95, 87, 92]mapping: dict[str, int] = {"a": 1, "b": 2}pair: tuple[str, int] = ("Alice", 30)unique: set[str] = {"red", "green", "blue"}Before Python 3.9 you had to write List[int], Dict[str, int] from typing. Prefer the lowercase built-in forms in Python 3.9+.
Optional — values that can be None
Section titled “Optional — values that can be None”Optional[T] means the value is either T or None. It is shorthand for Union[T, None].
from typing import Optional
def find_user(user_id: int) -> Optional[str]: users = {1: "Alice", 2: "Bob"} return users.get(user_id) # returns str or NoneIn Python 3.10+ you can write str | None instead:
def find_user(user_id: int) -> str | None: users = {1: "Alice", 2: "Bob"} return users.get(user_id)Both forms are equivalent. The X | None syntax is preferred in modern Python.
Union — one of several types
Section titled “Union — one of several types”Union[X, Y] means the value can be either X or Y. In Python 3.10+ use the | operator directly.
from typing import Union
def process(value: Union[int, str]) -> str: return str(value)
# Python 3.10+ shorthand:def process_modern(value: int | str) -> str: return str(value)Type aliases
Section titled “Type aliases”A type alias gives a meaningful name to a complex annotation, making signatures easier to read.
Vector = list[float]UserId = intUserMap = dict[UserId, str]
def scale(v: Vector, factor: float) -> Vector: return [x * factor for x in v]For Python 3.12+ use type statements:
type Vector = list[float] # Python 3.12+Full runnable demo
Section titled “Full runnable demo”from typing import Optional, Union
# --- variable annotations ---x: int = 10name: str = "Alice"scores: list[int] = [95, 87, 92]mapping: dict[str, int] = {"a": 1, "b": 2}
# --- function signatures ---def add(a: int, b: int) -> int: return a + b
def repeat(text: str, times: int = 1) -> str: return text * times
# --- Optional / X | None ---def find_user(user_id: int) -> Optional[str]: users = {1: "Alice", 2: "Bob"} return users.get(user_id)
# --- Union / | ---def process(value: Union[int, str]) -> str: return str(value)
# --- type alias ---Vector = list[float]
def scale(v: Vector, factor: float) -> Vector: return [x * factor for x in v]
# --- run it ---print(add(3, 4))print(repeat("Hi", 3))print(find_user(1))print(find_user(99))print(process(42))print(process("hello"))print(scale([1.0, 2.0, 3.0], 2.5))
# hints do NOT restrict runtime behaviourprint(add("oops", " still works")) # Python does not enforce at runtimeLoading Python runtime (first run only)…