A generic type is a type that takes another type as a parameter. We’ve used them throughout the course — list[int], dict[str, float]. This lesson covers them properly.

Built-in generics

Almost every collection type in Python accepts a generic parameter:

ages: list[int] = [25, 30, 35]
prices: dict[str, float] = {"apple": 0.5, "banana": 0.3}
unique_tags: set[str] = {"python", "ml"}
point: tuple[float, float] = (1.0, 2.0)

The square brackets read like a type-level call: list[int] is “a list of int”.

Modern Python (3.9+) — write the lowercase built-in names directly. Older tutorials use capitalised versions imported from typing (List[int], Dict[str, float]). Avoid that — the lowercase form is the modern standard.

Tuples — fixed vs variable length

A tuple’s type hint can either describe each position:

point: tuple[int, int] = (3, 4)              # two ints
record: tuple[str, int, str] = ("a", 1, "b") # three specific positions

Or describe a homogeneous tuple of any length:

nums: tuple[int, ...] = (1, 2, 3, 4, 5)     # ints, any count

The ... (literally three dots) means “and the same type continues”.

Generic function signatures

A function that takes a list of strings:

def total_length(words: list[str]) -> int:
    return sum(len(w) for w in words)

A function that takes any iterable:

from collections.abc import Iterable


def total_length(words: Iterable[str]) -> int:
    return sum(len(w) for w in words)

Iterable[str] is more flexible than list[str] — it accepts a list, tuple, set, generator, anything iterable. Use the broadest type that fits.

Useful types from collections.abc

For function parameters, prefer the abstract types over concrete ones:

from collections.abc import Iterable, Mapping, Sequence


def my_func(
    items: Iterable[int],          # anything you can iterate
    lookup: Mapping[str, int],     # any dict-like (reading only)
    ordered: Sequence[str],         # any indexable, len-able sequence
) -> None:
    ...

You’ll see these in real codebases. They make functions accept more inputs without losing type safety.

Writing your own generic function (TypeVar)

When a function works with any type but should preserve it across the call, use a type variable:

from typing import TypeVar


T = TypeVar("T")


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


print(first([1, 2, 3]))       # type is int
print(first(["a", "b"]))      # type is str

T is a placeholder. Pyright infers what T is for each call. The return type matches the element type.

Without T, you’d have to write first(items: list[object]) -> object: — and lose all type information at the call site.

Python 3.12+ — newer syntax

Python 3.12 introduced cleaner generic syntax:

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

The [T] is right after the function name — like generics in other languages. Either syntax works; the bracketed form is cleaner if you’re on a recent Python.

Writing your own generic class

from typing import TypeVar, Generic


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()


s: Stack[int] = Stack()
s.push(1)
s.push(2)
print(s.pop())   # 2  — pyright knows this is an int

The class works with any type, while keeping that type consistent. Stack[int] only accepts integers; Stack[str] only strings.

Python 3.12+ has the shorter form here too:

class Stack[T]:
    def __init__(self) -> None:
        self._items: list[T] = []

When to reach for generics

You won’t write generic classes every day. The common cases:

  • Container types (a stack, a queue, a cache)
  • Functions that pick or transform without changing element types (first, last, pick)
  • Library code aimed at being reusable

For application code, sticking with concrete types (list[User], dict[str, Product]) is usually clearer.

What’s next

Real code often deals with values that might be missing. Next — Optional and the X | None pattern.

Toggle theme (T)