Complete type hint reference. For a guided intro, Real Python — Python Type Checking is the best walkthrough available.
Python is dynamically typed — type hints are purely informational at runtime. They don't enforce anything by themselves. The value comes from:
mypy or pyright (like TypeScript's tsc)function greet(name: string): string {
return `Hello, ${name}!`;
}
function add(a: number, b: number): number {
return a + b;
}
// Enforced at compile time
greet(42); // TS Error!
def greet(name: str) -> str:
return f"Hello, {name}!"
def add(a: int, b: int) -> int:
return a + b
# NOT enforced at runtime — no error here
greet(42) # runs fine, but mypy would flag it
# Check with mypy:
# mypy script.py → error: Argument 1 to "greet" has incompatible type "int"
# Variables
name: str = "Alice"
age: int = 30
price: float = 9.99
active: bool = True
# Without assignment (declare intent)
user_id: int # declared but not yet assigned
# Functions
def multiply(x: int, y: int = 1) -> int:
return x * y
# No return value
def log(msg: str) -> None:
print(msg)
# Any type (opt out of checking)
from typing import Any
def process(data: Any) -> Any:
return data
// Arrays
const nums: number[] = [1, 2, 3];
const words: Array<string> = ["a"];
// Object / Record
const user: { name: string; age: number } = ...;
const map: Record<string, number> = {};
// Tuple
const pair: [string, number] = ["Alice", 30];
// Union
function id(x: string | number): string {...}
// Optional (string | undefined)
function find(x?: string): string | null {...}
# Lists, dicts, sets, tuples — use lowercase directly
nums: list[int] = [1, 2, 3]
words: list[str] = ["a"]
ages: dict[str, int] = {"Alice": 30}
ids: set[int] = {1, 2, 3}
# Tuple — specify each element type
point: tuple[int, int] = (3, 4)
triple: tuple[str, int, bool] = ("Alice", 30, True)
# Variable-length homogeneous tuple
coords: tuple[float, ...] = (1.0, 2.0, 3.0)
# Union (Python 3.10+ can use |)
def parse(x: str | int) -> str:
return str(x)
# Optional — can be None
def find(name: str | None = None) -> str | None:
...
Python 3.9+: use built-in list[int], dict[str, int] directly.
Python 3.8 and below: must import from typing: from typing import List, Dict, Optional, Union.
Python 3.10+: can use X | Y for union instead of Union[X, Y].
When a function receives or returns a dict with known keys, TypedDict is your interface or object type in TypeScript.
interface User {
name: string;
age: number;
email?: string; // optional
}
function process(user: User): string {
return user.name;
}
from typing import TypedDict
class User(TypedDict):
name: str
age: int
class UserWithEmail(User, total=False): # total=False → all optional
email: str
def process(user: User) -> str:
return user["name"]
# Or with Required/NotRequired (Python 3.11+)
from typing import Required, NotRequired
class Config(TypedDict):
host: Required[str]
port: NotRequired[int]
# Callable — type for functions
from typing import Callable
def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
return fn(a, b)
# fn takes two ints, returns an int
# Callable with any signature
def run(callback: Callable[..., None]) -> None:
callback()
# TypeVar — generic functions (like in TypeScript)
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
first([1, 2, 3]) # inferred as int
first(["a", "b"]) # inferred as str
# Python 3.12+ syntax
class Stack[T]:
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
# Python 3.11 and below
from typing import Generic, TypeVar
T = TypeVar("T")
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
Protocol is Python's answer to TypeScript's structural typing — define what an object must be able to do, not what it must inherit from.
interface Drawable {
draw(): void;
}
// Anything with a draw() method satisfies this
function render(shape: Drawable): void {
shape.draw();
}
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
# Any class with a draw() method satisfies this
# — no inheritance required (structural, not nominal)
class Circle:
def draw(self) -> None:
print("Drawing circle")
def render(shape: Drawable) -> None:
shape.draw()
render(Circle()) # ✅ — mypy is happy
# Literal — restrict to specific values (like const union in TS)
from typing import Literal
Direction = Literal["north", "south", "east", "west"]
def move(direction: Direction) -> None:
...
move("north") # ✅
move("up") # mypy error
# Final — constant (like const in TS)
from typing import Final
MAX_SIZE: Final = 100
MAX_SIZE = 200 # mypy error
# Type narrowing — mypy tracks isinstance checks
def process(val: int | str) -> str:
if isinstance(val, int):
return str(val * 2) # mypy knows val is int here
return val.upper() # mypy knows val is str here
# assert — narrow type (like TypeScript's type assertion)
assert isinstance(user, Admin) # mypy treats user as Admin after this
# Install
pip install mypy
# Check a file
mypy script.py
# Check a whole project
mypy .
# Strict mode (like TypeScript's strict: true)
mypy --strict script.py
# pyproject.toml config
[tool.mypy]
python_version = "3.12"
strict = true
ignore_missing_imports = true
If you use VS Code, install the Pylance extension — it uses pyright for type checking inline in the editor. Much faster feedback loop than running mypy manually. Set "python.analysis.typeCheckingMode": "basic" in your settings to start.
1. Do Python type hints enforce types at runtime?
2. In modern Python (3.9+), how do you annotate a list of strings?
3. What is Protocol used for?
4. Callable[[int, str], bool] describes a function that…
Questions answered correctly.