Basic Type Annotations
Annotate function parameters and return types with : type and -> type.
# Before type hints (ambiguous)
def add(a, b):
return a + b
# With type hints (self-documenting)
def add(a: int, b: int) -> int:
return a + b
def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! " * times).strip()
# Variable annotations
age: int = 25
name: str = "Alice"
prices: list[float] = [1.99, 2.49, 0.99]Type hints are optional and not enforced at runtime. Use them for documentation and tooling benefits.
The typing Module
For complex types use Optional, Union, Callable, Tuple.
from typing import Optional, Union, Callable, Tuple
# Optional: value can be None
def find_user(user_id: int) -> Optional[str]:
users = {1: "Alice", 2: "Bob"}
return users.get(user_id) # Returns str or None
# Union: one of several types
def process(value: Union[int, float, str]) -> str:
return str(value)
# Callable: function type
def apply(func: Callable[[int], int], n: int) -> int:
return func(n)
# Tuple with specific types
Point = Tuple[float, float]
def distance(p: Point) -> float:
return (p[0]**2 + p[1]**2) ** 0.5Modern Type Hints (Python 3.10+)
Python 3.10+ allows cleaner syntax for Union and Optional.
# Python 3.10+ — use | instead of Union
def process(value: int | float | str) -> str:
return str(value)
# Optional is just X | None
def find(id: int) -> str | None:
return None
# Built-in generics (no need to import from typing)
def average(numbers: list[float]) -> float:
return sum(numbers) / len(numbers)
def get_config() -> dict[str, str]:
return {"host": "localhost"}Type Hints with dataclasses
Type hints are required in dataclasses to define fields.
from dataclasses import dataclass
from typing import ClassVar
@dataclass
class Product:
name: str
price: float
quantity: int = 0
tax_rate: ClassVar[float] = 0.08 # Class variable
def total_price(self) -> float:
return self.price * (1 + self.tax_rate)
p = Product("Coffee", 3.50, 10)
print(p.name, p.total_price())Type Hints: Documentation Python Doesn't Enforce
The biggest surprise about type hints: Python ignores them at runtime. They're annotations for humans and tools, not checks. Pass a string where you hinted int and it runs fine — nothing raises. The value comes from static checkers like mypy and editor autocomplete.
def greet(name: str, times: int = 1) -> str:
return (name + " ") * times
greet(123, "oops") # runs! hints are NOT validated at runtime
| Hint | Means |
|---|---|
list[int] | list of ints |
dict[str, float] | str keys → float values |
Optional[str] / str | None | a str or None |
Callable[[int], str] | function int → str |
Why bother? On a big codebase, mypy catches "you passed the wrong type" before you ever run the program, and your editor can autocomplete and refactor safely. Start by hinting function signatures — that's where the payoff is highest. To actually validate at runtime you need a library like pydantic.
🏋️ Practical Exercise
Add type hints to your code:
- Annotate a function
add(a: int, b: int) -> int. - Hint a variable holding a list of strings using
list[str]. - Use
Optional(or| None) for a parameter that may beNone. - Run
mypyon the file and fix any reported type errors.
🔥 Challenge Exercise
Take an untyped function that processes a list of user records (dicts) and fully annotate it: use list, dict, Optional, and a TypedDict or dataclass for the record shape, plus a return type. Then introduce a deliberate type mismatch and confirm mypy catches it at check time even though Python runs it fine. Bonus: add a Callable type hint for a function passed as an argument.
📋 Summary
- Type hints annotate the expected types of variables, parameters, and return values.
- They are not enforced at runtime — Python ignores them during execution.
- Static checkers like mypy use them to catch type errors before running.
- The
typingmodule providesOptional,Union,Callable, and more (some now built-in). - Python 3.9+ allows built-in generics (
list[int]); 3.10+ allowsX | Yunion syntax. - Hints improve readability, editor autocompletion, and refactoring safety.
Interview Questions on Type Hinting
- What are type hints and are they enforced at runtime?
- What is the benefit of type hints if Python ignores them?
- What is the
typingmodule used for? - What is the difference between
Optional[X]andX | None? - What is a static type checker like mypy?
- How do you annotate a function that takes another function?
- What changed about type hints in Python 3.9 and 3.10?
Related Topics
FAQ
No. Python treats annotations as metadata and does not enforce them at runtime — a function hinted to take an int will still accept a string. Their value comes from static checkers, editors, and documentation.
Hints catch bugs early via tools like mypy, power IDE autocompletion and refactoring, and serve as always-accurate documentation. On large codebases they greatly reduce type-related errors.
Optional[int] and int | None? +They mean the same thing — a value that is an int or None. Optional comes from typing; the int | None syntax is the newer, cleaner form available in Python 3.10+.
mypy is a static type checker that reads your annotations and reports type inconsistencies without running the code. Adding it to CI catches a whole class of bugs before they reach production.

