Python TypedDict: Type Checking Dictionary Schemas

Python's typing.TypedDict enables static type checkers to validate dictionaries with fixed sets of keys and specific value types, offering a schema-enforcement mechanism without altering runtime behavior. While standard Python dictionaries are dynamic and traditionally typed homogenously using annotations like dict[str, Any], TypedDict provides fine-grained type safety for structured, heterogeneous dictionary data such as JSON payloads and database records.

The Problem with Traditional Dictionary Typing

In standard Python typing, the generic type dict[K, V] assumes that all keys share the type K and all values share the type V. In real-world applications, structured data rarely follows this pattern. For example, an API response often has string keys mapped to varied types:

user = {
    "id": 101,
    "name": "Alice",
    "is_active": True
}

Annotating this object as dict[str, Any] loses all structural safety: static analyzers such as Mypy or Pyright cannot verify that "id" is an integer, nor can they flag missing keys or spelling mistakes such as accessing user["is_activ"].

How TypedDict Solves Schema Validation

TypedDict allows developers to define a type schema using class syntax or functional syntax. Static type checkers treat instances of this type as plain dictionaries at runtime, but enforce key presence and value types during static analysis.

from typing import TypedDict

class UserProfile(TypedDict):
    id: int
    name: str
    is_active: bool

# Correct usage
user: UserProfile = {
    "id": 101,
    "name": "Alice",
    "is_active": True
}

# Type-checker errors:
# 1. Missing required key 'is_active'
# 2. Incompatible value type for 'id' (str instead of int)
invalid_user: UserProfile = {
    "id": "101",
    "name": "Bob"
}

Key Capabilities

  1. Static Key and Type Enforcement: Type checkers verify that all declared keys exist and their corresponding values match the declared types. Accessing an undefined key will trigger an error during static analysis.
  2. Handling Optional Keys: By default, all declared keys are required (total=True). Setting total=False makes all keys optional. For granular control in Python 3.11+, typing.Required and typing.NotRequired specify key requirements on an individual basis:
from typing import TypedDict, NotRequired

class Configuration(TypedDict):
    host: str
    port: int
    timeout: NotRequired[float]  # Optional key
  1. Zero Runtime Overhead: TypedDict creates standard dictionaries. It does not introduce runtime overhead, instantiate custom classes, or perform runtime validation like pydantic or dataclasses. At runtime, isinstance(user, UserProfile) raises a TypeError because TypedDict is purely an analysis construct.

Practical Role in Python Codebases

TypedDict bridges the gap between dynamic dictionaries and strict models. It is ideal for codebases consuming external APIs, parsing configuration files, or interacting with legacy systems where converting raw dictionaries into class instances would add unnecessary deserialization cost or break backwards compatibility.