Skip to content

Type Hints

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 = 10
name: str = "Alice"

The : int and : str have zero effect on execution. They exist solely to communicate intent.

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.

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[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 None

In 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[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)

A type alias gives a meaningful name to a complex annotation, making signatures easier to read.

Vector = list[float]
UserId = int
UserMap = 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+
from typing import Optional, Union
# --- variable annotations ---
x: int = 10
name: 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 behaviour
print(add("oops", " still works")) # Python does not enforce at runtime
What is the modern Python 3.10+ syntax for 'str or None'?
What does Python do if you pass a string to a parameter annotated as `int`?
Which of the following is the correct Python 3.9+ annotation for a list of integers?
What is a type alias?