One of the most common data structures in Python is the humble dictionary. It’s flexible, dynamic, and perfect for quick key-value mappings. But as projects grow, that flexibility turns into a liability - courtesy, misspelled keys, wrong value types, and messy JSON-like structures.
That’s where TypedDict comes in.
Let's start with a normal dict -
user = {"name": "Alice", "age": 30, "is_active": True}
Looks fine. But Python won’t stop us from writing:
user = {"nam": "Alice", "age": "thirty"}
The code runs, but now your dict is broken. Type checkers like mypy or pyright can’t help either, because plain dict doesn’t carry key-level type info.
Enter TypedDict
TypedDict was introduced in PEP 589 to apparently solve exactly this kind of problem. It lets us define the shape of a dictionary:
from typing import TypedDict
class User(TypedDict):
name: str
age: int
is_active: bool
Now, I can annotate dictionaries like this:
user: User = {"name": "Abhi", "age": 35, "is_active": True} # ✅ Valid
user: User = {"nam": "Abhi", "age": "thirty"} # ❌ Type checker error
At runtime, it’s still a dict. But my editor/type checker will catch mistakes before they reach production.
Required vs Optional Keys
By default, all keys are required. But sometimes, we want flexibility.
All Optional
class Movie(TypedDict, total=False):
title: str
year: int
rating: float
Here, title, year, and rating are all optional.
Mixing Required & Optional
Python 3.11+ added explicit control with Required and NotRequired:
from typing import Required, NotRequired
class Movie(TypedDict):
title: Required[str] #must exist
year: NotRequired[int] # optional
rating: float | None # can be None
Real-World Example: API Response
Now where can we actually use this? Imagine you’re working with an API that returns a user profile:
# API returns JSON:
{
"id": 101,
"username": "abhi",
"email": "abhi@example.com",
"is_active": true
}
We can model this with TypedDict:
class UserProfile(TypedDict):
id: int
username: str
email: str
is_active: bool
This helps with editor autocompletion and type safety:
def send_welcome(user: UserProfile) -> None:
if user["is_active"]:
print(f"Welcome, {user['username']}!")
Where are they limited?
TypedDicts are purely static typing tools. At runtime, Python doesn’t enforce them:
user: User = {"name": "Alice", "age": "thirty", "is_active": True}
# ✅ Runs fine, ❌ Type checker complains
So if you don't pay attention to your type checker, it doesn't really matter - unlike when you use say, pydantic or attrs, both of which enforce runtime validation.
TypedDicts strike a balance between plain dicts (too loose) and dataclasses (too rigid). They can help us:
Catch typos in dictionary keys
→ "usrname" instead of "username" will be flagged before runtime.
Define API schemas and configs cleanly
→ Great when modeling responses from JSON APIs or configuration files.
Keep dicts serializable
→ Unlike classes, these remain JSON-compatible out of the box.
If your codebase deals with lots of JSON or metadata dicts, TypedDicts can help leverage our type checker to save you from subtle bugs.
Thanks for reading Everything Python! Subscribe for free to receive new posts and support my work.