stackademic

The leading education platform for anyone with an interest in software development.

Type Hints

Annotate code with static types for tooling, clarity, and safer refactors

Overview

Type hints annotate variables, parameters, and return values so tools like mypy and Pyright can catch bugs before runtime. They are not enforced by the interpreter—Python stays dynamically typed—but they document intent and power editor autocomplete. Modern syntax (Python 3.10+) supports built-in generics and the X | Y union operator.

Syntax / Usage

Annotate with a colon for values and an arrow for return types. Use typing for generics, optionals, and callables.

from typing import Optional, Callable

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

count: int = 0
names: list[str] = ["ada", "linus"]
scores: dict[str, float] = {"ada": 9.5}

# Optional[T] is shorthand for T | None
def find(users: list[str], target: str) -> Optional[int]:
    return users.index(target) if target in users else None

# Callable[[arg types], return type]
transform: Callable[[int], int] = lambda x: x * 2

Run a static checker with mypy your_module.py; the interpreter ignores the hints at runtime.

Examples

Generic function preserving the input type with a TypeVar:

from typing import TypeVar

T = TypeVar("T")

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

reveal_x = first([1, 2, 3])      # inferred as int
reveal_y = first(["a", "b"])     # inferred as str

A Protocol for structural typing—any object with the right shape matches:

from typing import Protocol

class Sized(Protocol):
    def __len__(self) -> int: ...

def describe(obj: Sized) -> str:
    return f"length is {len(obj)}"

describe([1, 2, 3])   # lists satisfy Sized
describe("hello")     # so do strings

Common Mistakes

  • Believing hints are enforced at runtime; they are ignored unless a checker runs
  • Using bare list or dict instead of parameterized list[str], losing element type info
  • Reaching for Any too often, which silently disables checking for that value
  • Forgetting Optional (or | None) when a value can be None, causing "possibly None" errors
  • Writing circular imports for type-only references instead of using from __future__ import annotations

See Also

python-dataclasses python-functions python-classes