TypedDict & NamedTuple
TypedDict adds static shape to dicts - perfect for JSON blobs. NamedTuple provides immutable records lighter than dataclasses for read-only rows.
Recipe
from typing import TypedDict
class UserRow(TypedDict):
id: int
email: str
def load(row: UserRow) -> str:
return row["email"]When to reach for this:
- Typing
response.json()structures - CSV/JSON pipeline rows as dicts
- Immutable lightweight records from tuples
- Gradual typing without changing runtime types
Working Example
from typing import NamedTuple, NotRequired, TypedDict
class Address(TypedDict, total=False):
street: str
city: str
zip: NotRequired[str]
class User(TypedDict):
id: int
name: str
address: Address
class Point(NamedTuple):
x: int
y: int
def format_user(user: User) -> str:
city = user.get("address", {}).get("city", "unknown")
return f"{user['name']} ({city})"
def distance(a: Point, b: Point) -> float:
return ((a.x - b.x) ** 2 + (a.y - b.y) ** 2) ** 0.5
if __name__ == "__main__":
row: User = {"id": 1, "name": "Ada", "address": {"city": "London"}}
print(format_user(row))
print(distance(Point(0, 0), Point(3, 4)))What this demonstrates:
total=Falsemakes Address keys optional staticallyNotRequiredmarks optional keys in otherwise total TypedDict (3.11+)- TypedDict still dict at runtime -
user["name"]access - NamedTuple fields accessed as attributes with tuple unpacking
Deep Dive
How It Works
- TypedDict - Pure typing construct; no runtime validation by default.
- total flag - All keys required when
total=True(default). - Inheritance - TypedDict can extend other TypedDicts for nested API shapes.
- NamedTuple - Subclasses tuple; immutable; can have methods sparingly.
- typing_extensions - Backports for older Python; 3.14 has NotRequired in typing.
Pick Record Type
| Type | Runtime | Mutable |
|---|---|---|
| TypedDict | dict | Yes |
| NamedTuple | tuple | No |
| dataclass | object | Configurable |
| Pydantic | BaseModel | Yes |
Python Notes
# Required key in partial TypedDict (3.11+)
class Config(TypedDict, total=False):
debug: NotRequired[bool]
host: str # required if using Required[] in partial patternsGotchas
- TypedDict no runtime validation - Wrong types pass until used. Fix: Pydantic at boundaries.
- Extra keys allowed - TypedDict does not forbid unknown keys unless closed TypedDict (PEP 728+ features vary). Fix: Validate externally.
- NamedTuple mutable fields if not careful - Fields immutable but object fields could hold mutables. Fix: Tuple of immutables.
- TypedDict vs dataclass JSON - TypedDict stays dict for APIs expecting Mapping. Fix: Pick based on consumer.
- Key typos - mypy catches
user["emial"]; runtime still KeyError. Fix: Static check + tests.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Pydantic BaseModel | HTTP validation | Hot loop dict access |
| dataclass | Methods + defaults | Must remain dict |
| attrs | attrs ecosystem | Stdlib minimal |
| plain dict[str, Any] | Prototype only | Production API |
FAQs
TypedDict or dataclass?
TypedDict when value must stay dict (JSON APIs). dataclass for domain objects with behavior.
NamedTuple or dataclass frozen?
NamedTuple lighter tuple semantics; frozen dataclass when defaults and methods needed.
optional keys TypedDict?
total=False on whole dict or NotRequired per key in 3.11+.
nested TypedDict?
Compose smaller TypedDicts as field types - mirrors JSON nesting.
convert TypedDict to dataclass?
Manual mapping or Pydantic model_validate bridging layers.
NamedTuple typing fields?
Annotate in class body - NamedTuple supports PEP 526 annotations.
TypedDict isinstance?
No runtime type - isinstance checks dict only. Use validation library.
csv DictReader typing?
Cast rows to TypedDict after validating required keys present.
total=False inheritance?
Child can add optional keys; understand required key rules across inheritance.
FastAPI response_model?
Pydantic preferred for OpenAPI; TypedDict for internal service dict contracts.
Related
- Built-in & Collection Generics - dict[str, T]
- dataclasses - mutable records
- Optional, Union & the | Operator - optional fields
- Literal, Final & Annotated - constrained dict values
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.