Skip to content

Function Annotations and Type Hints#

Annotations attach metadata to parameters and return values. Type hints (PEP 484+) use annotations to document expected types for static checkers (mypy, pyright) — they do not enforce types at runtime by default.

How to use this page

Basics in Intermediate Syntax. Deep dive: Type Hints and Annotations.

At a glance
Track Python Advanced → Advanced Functions
Sections 7 major topics
Outline Use the right-hand TOC to jump

Topics: Syntax · Modern typing (3.9+ / 3.10+) · Common type forms · TypedDict and dataclass hints · Forward references and quotes · Runtime checking (optional) · Best practices for interviews

  1. Syntax
  2. Modern typing (3.9+ / 3.10+)
  3. Common type forms
  4. TypedDict and dataclass hints
  5. Forward references and quotes
  6. Runtime checking (optional)
  7. Best practices for interviews

Syntax#

def greet(name: str, excited: bool = False) -> str:
    msg = f"Hello, {name}"
    return msg + "!" if excited else msg

greet.__annotations__
# {'name': <class 'str'>, 'excited': <class 'bool'>, 'return': <class 'str'>}

Annotations are stored at function definition time — any expression is valid:

def weird(x: "forward ref", y: int = 42) -> None: ...

Modern typing (3.9+ / 3.10+)#

# Built-in generics (3.9+)
def merge(a: list[int], b: list[int]) -> list[int]:
    return sorted(a + b)

# Union with | (3.10+)
def find(items: list[str], target: str) -> str | None:
    ...

# Callable
from collections.abc import Callable

def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
    return fn(a, b)

Common type forms#

Need Hint
Optional value T \| None or Optional[T]
Any type Any (escape hatch)
Dict map dict[str, int]
Fixed tuple tuple[int, int, str]
Variable tuple tuple[int, ...]
Literal values Literal["r", "w"]
Type variable TypeVar("T")
Protocol / structural Protocol class
from typing import TypeVar, Literal

T = TypeVar("T")

def first(items: list[T]) -> T:
    return items[0]

Mode = Literal["read", "write"]
def open_mode(mode: Mode) -> None: ...

TypedDict and dataclass hints#

from typing import TypedDict

class User(TypedDict):
    name: str
    age: int

def process(user: User) -> str:
    return user["name"]

Runtime: still a plain dict — checker enforces keys statically.


Forward references and quotes#

class Node:
    def __init__(self, val: int, next: "Node | None" = None):
        self.val = val
        self.next = next

# 3.7+ from __future__ import annotations — postpone evaluation
from __future__ import annotations

class Node:
    def __init__(self, val: int, next: Node | None = None):
        ...

Runtime checking (optional)#

Type hints are ignored at runtime unless you use:

  • mypy, pyright, pytype — static analysis
  • typeguard, beartype — runtime decorators
  • isinstance checks you write manually
def add(a: int, b: int) -> int:
    return a + b

add("1", "2")   # runs — returns "12" (str concat if overloaded?)
add("1", 2)     # TypeError at runtime for str + int

Best practices for interviews#

def two_sum(nums: list[int], target: int) -> list[int]:
    ...
  • Hint public function signatures
  • Use list[int] not List[int] in 3.9+ code
  • Don't over-annotate every local variable
  • Match platform Python version on assessments

Interview traps (quick reference)#

Trap What goes wrong Safe approach
Hints enforce runtime False sense of safety Use checker or validate
Any everywhere Defeats purpose Specific types
Mutable default + hint Still shared default bug None sentinel
list vs List Style/version mix Pick one per codebase

Mental model checklist#

  1. Where are annotations stored?
  2. Do hints slow down Python at runtime?
  3. What is X | None expressing?
  4. When use Callable[[int], str]?

What's next#

Topic Page
Full typing guide Type Hints and Annotations
Intermediate syntax Intermediate Syntax and Structures
Callable protocol Callable Objects