PythonType Hints & Annotations

Type Hints & Annotations

Python is dynamically typed — variables don't have a fixed type, and nothing checks types while your program runs. Type hints add optional, explicit type information to function signatures and variables, primarily to help humans and tools understand code better, not to change how Python executes it.

Why Type Hints Exist
  • Documentation: a signature like def total(prices: list[float]) -> float: tells readers exactly what to pass in and expect back, without digging through the function body.

  • Static analysis: tools like mypy and IDEs (VS Code, PyCharm) can catch type mismatches before you run the code, similar to a compiler in statically typed languages.

  • Editor support: autocompletion, inline documentation, and refactoring tools all work better when types are known.

Warning
Type hints are purely optional documentation for tools — Python's interpreter does **not** enforce them at runtime. You can annotate a parameter as `int` and pass a string anyway; the program will run and only fail later if the mismatched type causes an actual error.

Python
def add(a: int, b: int) -> int:
    return a + b


print(add(2, 3))        # 5 — used as intended
print(add("2", "3"))    # "23" — Python happily runs this, no error, no enforcement!
5
23
Basic Syntax

Annotate a parameter with name: type, and annotate the return type with -> type after the closing parenthesis.

Python
def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! " * times).strip()


print(greet("Ada"))
print(greet("Ada", times=3))
Hello, Ada!
Hello, Ada! Hello, Ada! Hello, Ada!

Variables can be annotated too, even without an initial value:

Python
age: int = 30
name: str
name = "Ada"
The `typing` Module Basics

Before Python 3.9, container types needed help from the typing module to express things like "a list of strings" or "this value might be None".

Python
from typing import List, Dict, Optional, Union


def get_names() -> List[str]:
    return ["Ada", "Grace"]


def get_scores() -> Dict[str, int]:
    return {"Ada": 95, "Grace": 98}


def find_user(user_id: int) -> Optional[str]:
    users = {1: "Ada"}
    return users.get(user_id)  # returns a str, or None if missing


def parse_id(value: Union[int, str]) -> int:
    return int(value)

Typing construct

Meaning

List[str]

A list containing strings

Dict[str, int]

A dict mapping strings to ints

Optional[str]

Either a str, or None — shorthand for Union[str, None]

Union[int, str]

Either an int or a str

Modern Syntax (Python 3.9 / 3.10+)

Newer Python versions let you use built-in container types directly as generics, and the | operator instead of Union/Optional — no typing import required for these cases.

Python
# Python 3.9+: built-in generics, no typing.List/Dict needed
def get_names() -> list[str]:
    return ["Ada", "Grace"]


def get_scores() -> dict[str, int]:
    return {"Ada": 95, "Grace": 98}


# Python 3.10+: the '|' operator replaces Union and Optional
def find_user(user_id: int) -> str | None:
    users = {1: "Ada"}
    return users.get(user_id)


def parse_id(value: int | str) -> int:
    return int(value)

Older syntax (typing module)

Modern syntax (3.9 / 3.10+)

List[str]

list[str]

Dict[str, int]

dict[str, int]

Optional[str]

str | None

Union[int, str]

int | str

Tip
Prefer the modern syntax (`list[int]`, `int | None`) for any project targeting Python 3.10+. It reads more naturally and avoids an extra `typing` import for common cases. Use the `typing` module forms only for compatibility with older Python versions, or for more advanced constructs `typing` still provides (like `Callable`, `TypeVar`, or `Protocol`).
Checking Types Statically with `mypy`

Since Python itself ignores type hints at runtime, a separate tool is needed to actually check them. mypy is the most widely used static type checker — it reads your annotations and reports mismatches without ever running your code.

Python
# calculator.py
def add(a: int, b: int) -> int:
    return a + b


result = add("2", "3")  # mypy will flag this as an error

Bash
pip install mypy
mypy calculator.py
calculator.py:5: error: Argument 1 to "add" has incompatible type "str"; expected "int"
Found 1 error in 1 file (checked 1 source file)
Note
Even though `mypy` reports an error here, running `python calculator.py` directly would execute without complaint (producing the string `"23"`) — mypy is a separate, optional analysis step, not part of the Python interpreter.
Quick Reference

Concept

Example

Parameter annotation

def f(x: int):

Return annotation

def f() -> str:

Variable annotation

age: int = 30

Optional value

x: int | None (or Optional[int])

Multiple possible types

x: int | str (or Union[int, str])

List of a type

list[str] (or List[str])

Example
A fully annotated function combining several typing features: a default argument, an optional return, and a modern union type.

Python
def find_first_negative(numbers: list[int], start: int = 0) -> int | None:
    """Return the first negative number at or after 'start', or None if there isn't one."""
    for n in numbers[start:]:
        if n < 0:
            return n
    return None


print(find_first_negative([3, 7, -2, 9]))
print(find_first_negative([3, 7, 9]))
-2
None
Warning
Type hints have zero effect on runtime behavior — no matter how thorough your annotations are, Python will never raise a `TypeError` just because an argument doesn't match its hint. If you need genuine runtime validation, you must write explicit checks (or use a library such as `pydantic`) in addition to the hints.