Skip to main content
🪶
PEP 604 · Speedrun · Python 3.10 (2021)

PEP 604 in 90 seconds — int | None replaces Optional[int]

The Optional import you never have to write again.

Before 3.10, every typed Python file had this litany at the top: ```python from typing import Optional, Union def find_user(uid: int) -> Optional[dict]: ... def parse(data: Union[str, bytes]) -> dict: ... ``` Two imports + verbose generics for the most common type-hinting cases. PEP 604 (Python 3.10, 2021) shipped the pipe operator at the type level: `int | None` and `str | bytes`. No imports needed.

It works **everywhere** types appear — function annotations, dataclasses, `cast()`, generics, AND **isinstance** (3.10+): ```python if isinstance(value, int | float): # legal — replaces (int, float) tuple return f"{value} is numeric" ``` Under the hood the pipe creates a `types.UnionType` instance. Pylance/Pyright/mypy all support it. Pydantic v2 reads it natively. Once your minimum Python is 3.10, you can `from typing import Optional, Union` your way to a leaner imports section.

Before — Python ≤ 3.9, typing.Optional + Union
from typing import Optional, Union, List

def find_user(uid: int) -> Optional[dict]:
    """Returns a user dict, or None if missing."""
    ...

def parse(data: Union[str, bytes]) -> dict:
    ...

# isinstance needed a tuple, not a Union
def classify(value):
    if isinstance(value, (int, float)):
        return "number"
    if isinstance(value, (str, bytes)):
        return "text"
    return "other"

# Generics with unions got verbose fast
def merge(items: List[Union[int, str, None]]) -> List[Union[int, str]]:
    return [x for x in items if x is not None]
After — Python 3.10+, pipe syntax everywhere
# No imports needed for the common cases.
def find_user(uid: int) -> dict | None:
    """Returns a user dict, or None if missing."""
    ...

def parse(data: str | bytes) -> dict:
    ...

# isinstance accepts the union form
def classify(value):
    if isinstance(value, int | float):
        return "number"
    if isinstance(value, str | bytes):
        return "text"
    return "other"

# Generics stay readable
def merge(items: list[int | str | None]) -> list[int | str]:
    return [x for x in items if x is not None]

🎯 Predict the output

What does this print? `isinstance(value, A | B)` checks if value is an instance of A OR B.

def classify(value):
    if isinstance(value, int | float):
        return "number"
    if isinstance(value, str | bytes):
        return "text"
    return "other"

print(classify(42))
print(classify("hi"))
print(classify(b"bytes"))
print(classify(3.14))
print(classify([1, 2, 3]))
Type hints in modern Python → Foundations track

Or speedrun another PEP

PEP 572Assignment as an expression — 3 lines become 1.
PEP 572 in 90 seconds — the walrus that ate your for-loop
PEP 484Python stays dynamic — but you can opt into typing.
PEP 484 in 90 seconds — type hints, without TypeScript-envy
PEP 622Switch statements are a 5% feature. Pattern matching is the 95%.
PEP 622 in 90 seconds — match / case isn't just a switch