cheat sheet

typing-extensions

Package-level reference for typing-extensions on PyPI — what's backported, install, version policy, and migration to stdlib.

typing-extensions

What it is

typing-extensions is the backport package for typing features that are either too new to be in the stdlib of supported Python versions, or are still in PEP-draft / experimental status. It mirrors the typing module's API surface and adds new constructs (Self, TypeAlias, NotRequired, Required, Concatenate, ParamSpec, LiteralString, etc.) before they make it into the stdlib — usually one or two Python releases ahead.

Reach for typing-extensions when you want to: use Self on Python 3.10 (stdlib has it from 3.11); use TypedDict with NotRequired keys on 3.10 (stdlib gets it in 3.11); use Concatenate and ParamSpec on 3.9 (stdlib has them from 3.10); or use any PEP-655 / 695 / 696 / 698 feature on a Python that doesn't yet ship it.

Install

bash
pip install typing-extensions

Output: (none — exits 0 on success; pure-Python, no runtime deps)

bash
uv add typing-extensions

Output: dependency resolved + added to pyproject.toml

bash
poetry add typing-extensions

Output: updated lockfile + virtualenv install

No optional extras — the package is a single pure-Python module.

Versioning & Python support

  • Current line is the 4.x series. Minor releases add new constructs and refine existing ones; major releases (rare) drop very-old Python versions.
  • Supports Python 3.8+ on recent releases; older Pythons can pin older typing-extensions.
  • The library is forward-deflating — as constructs land in the stdlib, the package re-exports the stdlib version on newer Pythons and provides its own implementation on older ones. Your code can from typing_extensions import Self on any supported version and get the right thing.

Package metadata

  • Maintainer: Python core developers (notably Guido van Rossum, Jelle Zijlstra)
  • Project home: github.com/python/typing_extensions
  • Docs: typing-extensions.readthedocs.io
  • PyPI: pypi.org/project/typing-extensions
  • License: PSF-2.0
  • Governance: Part of the CPython project; tracks the typing module's evolution
  • First released: 2017
  • Downloads: consistently in PyPI top 5 (used by pydantic, fastapi, mypy, starlette, every modern typed library)

Optional dependencies & extras

  • None. typing-extensions has no third-party runtime dependencies.

Alternatives

PackageTrade-off
Standard library typingAlways preferred when the construct you need is available in your minimum supported Python.
mypy_extensionsOlder, niche backports for mypy-specific constructs (TypedDict, Arg). Mostly superseded by typing-extensions.
Conditional sys.version_info guardsManual workaround. Verbose, brittle. Use typing-extensions instead.
typeguardDifferent problem — runtime type enforcement. Pair with typing-extensions rather than replace.

Common gotchas

  1. from typing_extensions import X works on any Python version even when X is already in typing — the backport re-exports the stdlib version. Use the import line that supports your oldest Python target.
  2. TypedDict has two flavors — class-based (class TD(TypedDict): x: int) and functional (TD = TypedDict("TD", {"x": int})). The functional form is required for keys that aren't valid Python identifiers.
  3. NotRequired and Required apply at the field level, not via separate base classes. class T(TypedDict): a: int; b: NotRequired[str].
  4. Self was added to stdlib in 3.11. Imports from typing_extensions continue to work but type checkers see them as the stdlib version when running under 3.11+.
  5. Annotated has been in stdlib typing since 3.9. typing_extensions.Annotated is identical on 3.9+.
  6. assert_never / assert_type are runtime-no-op helpers — they exist to direct type checkers, not enforce behavior at runtime.
  7. Concatenate and ParamSpec work only in type checkers; runtime introspection of them is limited.

Real-world recipes

The recipes cover the constructs most frequently backported in 2026 codebases.

Recipe 1 — TypedDict with required and optional keys.

python
from typing_extensions import TypedDict, NotRequired, Required

class User(TypedDict):
    id: int
    name: str
    email: NotRequired[str]    # may be omitted
    avatar_url: NotRequired[str]

# In TypedDict with total=False, every key is optional by default; use Required to invert
class PartialUser(TypedDict, total=False):
    id: Required[int]
    name: str

Output: type checkers (mypy / pyright) accept dicts that omit email; PartialUser requires id even when total=False. Runtime behavior is just a normal dict.

Recipe 2 — Annotated for parameter metadata.

python
from typing_extensions import Annotated
from dataclasses import dataclass

@dataclass
class Spec:
    min: int
    max: int

def clamp(x: int, bounds: Annotated[Spec, "default 0..100"] = Spec(0, 100)) -> int:
    return max(bounds.min, min(bounds.max, x))

print(clamp(150))

Output:

code
100

Annotated[T, metadata] lets type checkers see T while frameworks (pydantic, FastAPI) read the metadata. Same syntax that pydantic uses for field constraints.

Recipe 3 — Self for fluent / builder APIs.

python
from typing_extensions import Self

class QueryBuilder:
    def __init__(self) -> None:
        self._clauses: list[str] = []

    def where(self, clause: str) -> Self:
        self._clauses.append(clause)
        return self

    def build(self) -> str:
        return " AND ".join(self._clauses)

q = QueryBuilder().where("a=1").where("b=2").build()
print(q)

Output:

css
a=1 AND b=2

-> Self means subclasses are typed correctly too; MyBuilder().where(...) returns MyBuilder, not QueryBuilder.

Recipe 4 — Required / NotRequired for partial-update payloads.

python
from typing_extensions import TypedDict, NotRequired

class UpdateProfile(TypedDict):
    user_id: int
    name: NotRequired[str]
    email: NotRequired[str]
    bio: NotRequired[str]

def patch(data: UpdateProfile) -> None:
    for k, v in data.items():
        print(f"set {k} = {v!r}")

patch({"user_id": 42, "name": "Alice Dev"})

Output:

ini
set user_id = 42
set name = 'Alice Dev'

Common in REST PATCH endpoints — TypedDict captures "send any subset, but user_id is required".

Recipe 5 — Concatenate for parameter-spec aware decorators.

python
from typing_extensions import ParamSpec, Concatenate
from typing import Callable, TypeVar

P = ParamSpec("P")
R = TypeVar("R")

def with_request_id(fn: Callable[Concatenate[str, P], R]) -> Callable[P, R]:
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        request_id = "req-1234"
        return fn(request_id, *args, **kwargs)
    return wrapper

@with_request_id
def handler(request_id: str, payload: dict) -> int:
    print(request_id, payload)
    return 200

print(handler({"x": 1}))      # request_id is supplied by the decorator

Output:

css
req-1234 {'x': 1}
200

Concatenate[str, P] says "the wrapped function takes a string followed by P". Type checkers verify the call site sees only P after wrapping.

Recipe 6 — assert_never for exhaustive match.

python
from typing_extensions import assert_never, Literal

Color = Literal["red", "green", "blue"]

def to_hex(c: Color) -> str:
    if c == "red": return "#ff0000"
    if c == "green": return "#00ff00"
    if c == "blue": return "#0000ff"
    assert_never(c)    # mypy / pyright errors if a case is missing

Output: runtime never reaches assert_never for valid Color values; type checkers verify the function is exhaustive at edit time.

Performance tuning

typing-extensions itself has no runtime perf — most constructs are no-ops at runtime. The overhead is in:

  • TypedDict validation libraries (pydantic, msgspec) that consume the annotations. Tune those, not typing-extensions.
  • Annotated metadata extractionget_type_hints(..., include_extras=True) is the canonical way.
  • runtime_checkable Protocols — using isinstance(x, MyProtocol) is slower than nominal isinstance. Reserve for true duck-typing tests.

Version migration guide

The migration story is "remove typing_extensions imports as you raise your Python floor":

  • 3.8 floorLiteral, TypedDict, Final, Protocol, runtime_checkable move to stdlib.
  • 3.9 floorAnnotated moves to stdlib. list[int] / dict[str, T] generics work directly without from __future__ import annotations.
  • 3.10 floorParamSpec, Concatenate, TypeAlias, TypeGuard, is_typeddict move to stdlib.
  • 3.11 floorSelf, Never, Required, NotRequired, Unpack, assert_never, assert_type move to stdlib.
  • 3.12 floorTypeAliasType, override move to stdlib.
  • 3.13 floorReadOnly for TypedDict, TypeIs move to stdlib.
python
# Pre-3.11
from typing_extensions import Self

# 3.11+: stdlib equivalent
from typing import Self

Output: functionally identical; the stdlib path avoids the third-party dependency. Linting rules like ruff's UP037 can flag these.

Security considerations

  • typing-extensions has no runtime side effects. No filesystem, no network. Practically the safest possible dependency.
  • Don't use runtime_checkable Protocols for security-sensitive checks. They check method existence only, not signature compatibility.
  • Annotated metadata is untyped at runtime. Anything that consumes metadata (pydantic, etc.) is responsible for validating it; don't trust attacker-controlled metadata.

Testing & CI

Type-checking is the entire point. CI integration is via mypy / pyright:

bash
# pip install mypy
mypy --strict src/

Output:

yaml
Success: no issues found in N source files.

Failures show specific lines where the type checker disagreed with the annotation — usually a missing NotRequired, a mis-named Self, or a forgotten assert_never.

Ecosystem integrations

  • mypy, pyright, pyre — all recognize typing_extensions constructs and treat them identically to stdlib equivalents.
  • pydanticAnnotated is the primary mechanism for field constraints (e.g. Annotated[int, Field(ge=0)]).
  • fastapi — uses Annotated for Depends, Query, Path parameter metadata.
  • msgspec — same Annotated pattern.
  • sqlalchemy 2.x — uses Annotated for mapped column types.
  • starlette, litestar — depend on it via pydantic.

Compatibility matrix

Pythontyping-extensionsNotes
3.64.0.x (frozen)Final supported line for 3.6.
3.74.7.x (frozen)Final line for 3.7.
3.84.xLowest current floor.
3.94.xStable.
3.104.xStable.
3.114.xStable.
3.124.xStable.
3.134.xStable; new constructs land here first.

Production deployment

  • Pin a minimum version in libraries (typing-extensions>=4.6) so consumers get the constructs you actually use. Tight pins cause solver conflicts.
  • Keep the import idiomatic. from typing_extensions import X — never import typing_extensions as te; te.X.
  • Track stdlib parity. When you raise your Python floor, run ruff --select UP037 (or equivalent) to migrate imports back to stdlib.
  • Don't conditional-import. try: from typing import X except: from typing_extensions import X is brittle and unnecessary — just import from typing_extensions always; it re-exports the stdlib version on newer Pythons.

When NOT to use this

  • Your minimum Python is 3.13+ — most useful constructs are stdlib already; the package mostly forwards.
  • You don't use type hints — the entire library is no-ops without a type checker.
  • You're shipping a single-file script for a known Python version — direct from typing import ... is simpler.

Troubleshooting common errors

Error / SymptomLikely causeFix
ImportError: cannot import name 'Self' from 'typing'Running on Python <3.11Import from typing_extensions instead.
Type checker says Self is unknownType checker too oldUpgrade mypy/pyright.
TypedDict accepts arbitrary keys at runtimeTypedDict is checker-onlyUse pydantic or msgspec for runtime validation.
NotRequired ignored at runtimeNotRequired is checker-onlySame — use a validation library.
pip install resolves an old typing-extensionsOld transitive pinInspect with pip show typing-extensions; bump library that pins old version.

See also

  • Python: typing — stdlib typing fundamentals
  • Python: mypy — static type checker
  • Official typing-extensions docs
  • PEP 655 — Required / NotRequired