A Python KeyError is raised when you access a dictionary with a key that doesn't exist. It's one of the most common runtime errors — especially when parsing API responses, JSON payloads, or configuration files. This guide covers every pattern you need to handle it safely.
What is a KeyError?
When you access dict[key] and that key doesn't exist, Python raises a KeyError. The error message shows the missing key name.
user = {"name": "Alice", "email": "alice@example.com"}
# This raises KeyError: 'age'
print(user["age"])
# Traceback:
# KeyError: 'age'Fix 1: Use dict.get() — The Safest Pattern
The .get() method returnsNone if the key is missing, or a default value you specify. It never raises a KeyError.
user = {"name": "Alice"}
# Returns None — no error
age = user.get("age")
# Returns a default value
age = user.get("age", 0)
role = user.get("role", "viewer")
print(age) # 0
print(role) # "viewer"Fix 2: Check with 'in' Before Accessing
Use the in operator to guard access. Best when you need different logic for present vs absent keys.
data = {"status": "active", "score": 95}
if "score" in data:
print(f"Score: {data['score']}")
else:
print("Score not available")
# Combine with walrus operator (Python 3.8+)
if (score := data.get("score")) is not None:
print(f"Score: {score}")Fix 3: try/except KeyError
Use try/except when you want to log the error or handle it differently from other exceptions. Always log the full payload for debugging.
import logging
def get_user_email(data: dict) -> str:
try:
return data["user"]["email"]
except KeyError as e:
logging.warning("Missing key in user data: %s | payload: %s", e, data)
return ""
result = get_user_email({"user": {"name": "Bob"}})
print(result) # "" — no crash, warning loggedFix 4: Nested Dictionaries — Chain get()
For nested dicts, chain .get() calls. Each intermediate .get("key", ) returns an empty dict on a missing key, so the next .get() has something to call on.
response = {
"data": {
"user": {
"profile": {"city": "New York"}
}
}
}
# Safe — returns "" if any level is missing
city = (
response
.get("data", {})
.get("user", {})
.get("profile", {})
.get("city", "")
)
print(city) # "New York"
# Missing path — no KeyError, returns ""
country = (
response
.get("data", {})
.get("user", {})
.get("profile", {})
.get("country", "")
)
print(country) # ""Fix 5: Use defaultdict for Counters
defaultdict from the collections module auto-creates a default value when a missing key is accessed — no KeyError, no pre-initialization needed.
from collections import defaultdict
# Count word frequency — no KeyError on first encounter
word_counts = defaultdict(int)
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
for word in words:
word_counts[word] += 1
print(dict(word_counts))
# {'apple': 3, 'banana': 2, 'cherry': 1}
# Group items by category
grouped = defaultdict(list)
items = [("fruit", "apple"), ("veg", "carrot"), ("fruit", "banana")]
for category, item in items:
grouped[category].append(item)
print(dict(grouped))
# {'fruit': ['apple', 'banana'], 'veg': ['carrot']}Fix 6: setdefault() for Auto-Initialization
setdefault(key, default) sets a key only if it doesn't exist yet, then returns the value. Useful for building nested structures incrementally.
# Build a nested dict without KeyError
index = {}
records = [("UK", "London"), ("UK", "Manchester"), ("US", "New York")]
for country, city in records:
index.setdefault(country, []).append(city)
print(index)
# {'UK': ['London', 'Manchester'], 'US': ['New York']}
# Also useful for caching
cache = {}
def get_config(key: str) -> dict:
return cache.setdefault(key, {"loaded": False})Fix 7: API Responses — Always Validate Structure
Never assume an API response has all the expected keys. Check the shape of the response before accessing nested keys.
import requests
def fetch_user(user_id: int) -> dict:
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status()
data = response.json()
# Validate structure before accessing
if not isinstance(data, dict):
raise ValueError(f"Expected dict, got {type(data)}")
user = data.get("user") or {}
return {
"id": user.get("id", user_id),
"name": user.get("name", "Unknown"),
"email": user.get("email", ""),
"role": user.get("role", "viewer"),
}KeyError in API Responses — Real Example
APIs often return different shapes depending on success, error state, or version. Here's a defensive parser for a typical paginated API response:
Fragile — crashes on unexpected shape:
# KeyError if "results" or "items" is absent data = response.json() users = data["results"]["items"] total = data["results"]["total_count"]
Defensive — handles missing keys gracefully:
import logging
def parse_paginated_response(data: dict) -> dict:
results = data.get("results") or {}
return {
"items": results.get("items") or [],
"total": results.get("total_count", 0),
"page": results.get("page", 1),
"has_more": results.get("has_more", False),
}
try:
data = response.json()
parsed = parse_paginated_response(data)
except Exception as e:
logging.error("Failed to parse response: %s | body: %s", e, response.text[:500])
parsed = {"items": [], "total": 0, "page": 1, "has_more": False}Python vs JavaScript — Similar Error, Different Fix
| Language | Error | Safe Access |
|---|---|---|
| Python | KeyError: 'key' | dict.get('key', default) |
| JavaScript | TypeError: Cannot read properties of undefined | obj?.key ?? 'default' |
Validate your JSON API responses
Paste your JSON and instantly check its structure before writing Python parsing code.
Validate your JSON API responses →Frequently Asked Questions
What causes a Python KeyError?
A KeyError is raised when you access a dictionary with a key that doesn't exist —data["missing"]instead of data.get("missing"). It commonly occurs when API responses or JSON objects have optional or variable fields.
What is the difference between dict[key] and dict.get(key)?
dict[key] raises KeyError if the key is absent. dict.get(key) returns None, and dict.get(key, default) returns your fallback value. Use .get() when the key's presence is uncertain.
How do I handle KeyError in a loop?
Use dict.get(key, default) inside the loop, or use defaultdict from collections when building aggregations. If you need per-item error handling, wrap access in try/except and log the failing item.
What is defaultdict and when should I use it?
defaultdict(int) auto-initializes missing keys to 0, defaultdict(list) to an empty list. Use it for counters, grouping, and accumulation patterns where you'd otherwise need to check key existence on every iteration.
How do I fix KeyError in a nested dictionary?
Chain .get() calls with empty dict fallbacks: data.get("a", ).get("b", ""). Each level returns a safe empty dict if the key is missing, so the chain never raises KeyError.