Your script was adding up an order, then it stopped dead with KeyError: 'mango'. That’s Python saying the dictionary has no such key. The right fix depends on whether that key should exist.
Quick answer: a Python dictionary KeyError happens when d[key] asks for a key that isn’t there. Check it first with key in d, read it with d.get(key, default), or catch it with try / except KeyError:
price = prices.get("mango", 0.0) # no KeyError, 0.0 when missing
In this tutorial, we’ll fix a Python dictionary KeyError the way our team does it on real projects: reproduce it, find out why the key is missing, then pick the fix. Outputs come from runs on Python 3.12.5 and pandas 3.0.6.
What You Need Before You Start Fixing KeyError
- Python 3.8 or newer. Nothing here needs a newer version; we tested on 3.12.5.
- pandas, only for the one pandas example near the end.
- A sample to play with. We’ll use one small grocery price list the whole way through.
Here’s that running sample. Three fruits have prices, and the order asks for a fourth one that isn’t on the list:
prices = {"apple": 1.25, "banana": 0.59, "cherry": 4.99}
order = ["apple", "cherry", "mango"]
If dictionaries are new to you, my guide to creating dictionaries with dict() covers the basics in a few minutes.
Step 1: Reproduce the Python dictionary KeyError and read the traceback
I always start by running the failing code on purpose, and you should too. This loop totals the order with square brackets, which is exactly what raises a KeyError:
prices = {"apple": 1.25, "banana": 0.59, "cherry": 4.99}
order = ["apple", "cherry", "mango"]
total = 0
for item in order:
total += prices[item]
print(f"{item:<7} ${prices[item]:.2f}")
print(f"Total: ${total:.2f}")
Output:
apple $1.25
cherry $4.99
Traceback (most recent call last):
File "C:\pyguides\keyerror_order.py", line 6, in <module>
total += prices[item]
~~~~~~^^^^^^
KeyError: 'mango'

KeyError: 'mango' before any total is printed.Read the last line of your traceback first. Python shows the missing key in quotes, so 'mango' is a string key. The line above it points to total += prices[item], the exact lookup that failed.
Notice the first two lines printed, then nothing. An unhandled KeyError stops the whole script, just like the other types of errors in Python.
Step 2: Find out why the key is missing from the dictionary
Before you patch anything, find the cause. In my experience it’s almost never truly missing data. It’s a capital letter, a trailing space, or an integer key looked up as a string:
import difflib
prices = {"apple": 1.25, "Banana": 0.59, "cherry ": 4.99, 101: 2.49}
print("Keys:", ", ".join(repr(k) for k in prices)) # repr shows case, spaces, type
for wanted in ["banana", "cherry", "101"]:
hint = difflib.get_close_matches(wanted, [str(k) for k in prices], n=1, cutoff=0.6)
print(f"{wanted!r:>9} in prices? {wanted in prices} closest: {hint}")
clean = {str(k).strip().lower(): v for k, v in prices.items()} # normalize once
print(clean["banana"], clean["cherry"], clean["101"])
Output:
Keys: 'apple', 'Banana', 'cherry ', 101
'banana' in prices? False closest: ['Banana']
'cherry' in prices? False closest: ['cherry ']
'101' in prices? False closest: ['101']
0.59 4.99 2.49

repr() exposes the capital B, the trailing space and the integer key; difflib suggests the closest real key for each lookup.repr() is the trick here, because 'cherry ' and 'cherry' look identical in a plain print. Normalizing the keys once with strip().lower() makes all three lookups work.
When the dictionary is large, looping through the dictionary and printing each key with repr() finds the odd one fast.
Step 3: Check the dictionary key with the in operator first
Same price list, same order. The in operator checks the keys without raising anything, so you get to decide what happens to the missing item:
prices = {"apple": 1.25, "banana": 0.59, "cherry": 4.99}
order = ["apple", "cherry", "mango"]
total = 0
for item in order:
if item in prices:
total += prices[item]
else:
print(f"Skipped {item}: not on the price list")
print(f"Total: ${total:.2f}")
Output:
Skipped mango: not on the price list
Total: $6.24
The total is $6.24 and mango gets a clear message instead of a crash. Use this when your missing key needs its own branch, such as a warning, a log line or a different calculation.
There’s more on membership tests, including values and several keys at once, in checking if a Python dictionary contains a key.
Step 4: Use dict.get() with a default value
When a missing key simply means “use a default”, get() is the shortest fix. It returns None, or the default you pass, and never raises:
prices = {"apple": 1.25, "banana": 0.59, "cherry": 4.99}
order = ["apple", "cherry", "mango"]
print(prices.get("mango")) # None, no error
print(prices.get("mango", 0.0)) # your own default
total = sum(prices.get(item, 0.0) for item in order)
print(f"Total: ${total:.2f}")
discounts = {"apple": None, "banana": 0.10}
print(discounts.get("apple", 0.0), discounts.get("cherry", 0.0)) # stored None wins
Output:
None
0.0
Total: $6.24
None 0.0
The order total comes out at $6.24 again, in one line. Watch the last line, though: get("apple", 0.0) returned None, because the key exists and its stored value is None.
So get()‘s default only covers missing keys, not empty values. If None itself is the key you’re looking up, see how to fix KeyError: None in a dictionary.
Step 5: Catch KeyError with try and except
Sometimes you want the normal path to stay clean and handle the rare miss separately. That’s what try / except KeyError gives you:
prices = {"apple": 1.25, "banana": 0.59, "cherry": 4.99}
order = ["apple", "cherry", "mango"]
import logging
logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO)
def line_price(item):
try:
price = prices[item]
except KeyError as err:
logging.warning("no price for %s (KeyError %s)", item, err)
return 0.0
else:
return price
total = sum(line_price(item) for item in order)
print(f"Total: ${total:.2f}")
try:
prices["mango"]
except KeyError as err:
print("err.args[0] =", err.args[0], "| str(err) =", str(err))
Output:
WARNING: no price for mango (KeyError 'mango')
Total: $6.24
err.args[0] = mango | str(err) = 'mango'

str(err) keeps the quotes around the key.I catch KeyError only, never a bare except. The else block runs only when the lookup worked, so the try body stays one line long and can’t hide another bug.
Note: str(err) is "'mango'", quotes included. Use err.args[0] when you need the raw key. To log the full traceback instead, see printing a stack trace in Python.
Step 6: Fill missing keys with setdefault() or defaultdict
Counting and grouping are the classic KeyError traps, because counts[item] += 1 has to read the key before it can add to it. Here’s the same fruit list as a day of sales:
from collections import defaultdict
sales = ["apple", "cherry", "apple", "banana", "apple"]
counts = {}
try:
for item in sales:
counts[item] += 1
except KeyError as err:
print("Plain dict fails on the first new key:", err)
counts = {}
for item in sales:
counts.setdefault(item, 0)
counts[item] += 1
print("setdefault: ", counts)
by_item = defaultdict(int)
for item in sales:
by_item[item] += 1
print("defaultdict:", dict(by_item))
print("Reading 'mango' gives", by_item["mango"], "and adds it:", dict(by_item))
Output:
Plain dict fails on the first new key: 'apple'
setdefault: {'apple': 3, 'cherry': 1, 'banana': 1}
defaultdict: {'apple': 3, 'cherry': 1, 'banana': 1}
Reading 'mango' gives 0 and adds it: {'apple': 3, 'cherry': 1, 'banana': 1, 'mango': 0}

setdefault() and defaultdict(int) both count three apples, one cherry and one banana.Both fixes give you the same counts. I reach for defaultdict in loops, and setdefault() when the dictionary comes from somewhere else and has to stay a plain dict.
The last line shows the catch: just reading by_item["mango"] added mango with a count of 0. More counter patterns are in incrementing a value in a Python dictionary.
If you want a default without that side effect, subclass dict and add __missing__. Python calls it only when a bracket lookup misses:
class PriceList(dict):
def __missing__(self, key): # called by d[key] when the key isn't there
return 0.0
prices = PriceList(apple=1.25, banana=0.59, cherry=4.99)
print(prices["mango"], "mango" in prices, len(prices))
Output:
0.0 False 3
Mango reads as 0.0, but it isn’t stored, so the length stays 3.
Step 7: Handle KeyError in nested dictionaries and JSON
API responses and JSON files are where nested KeyErrors live. Here’s a real-looking order from a Denver customer, with no phone number in it:
import json
payload = """{"order_id": 5012,
"customer": {"name": "Emily Carter",
"address": {"city": "Denver", "zip": "80202"}},
"items": [{"sku": "apple", "qty": 3}]}"""
order = json.loads(payload)
print(order["customer"]["address"]["city"])
try:
print(order["customer"]["phone"]["mobile"])
except KeyError as err:
print("KeyError:", err)
mobile = order.get("customer", {}).get("phone", {}).get("mobile", "n/a")
print("Mobile:", mobile)
def dig(data, *path, default=None):
for step in path:
try:
data = data[step]
except (KeyError, IndexError, TypeError):
return default
return data
print(dig(order, "items", 0, "qty"), dig(order, "items", 5, "qty", default=0),
dig(order, "customer", "address", "zip"))
Output:
Denver
KeyError: 'phone'
Mobile: n/a
3 0 80202

phone key raises KeyError, and chained get() calls or dig() return a default instead.Chained get() works, but you need an empty {} fallback at each level. The dig() helper also walks lists, and it catches IndexError and TypeError for a missing list item or a None in the path.
For dictionaries you build yourself, fixing KeyError while generating a nested dictionary goes deeper. Loading the JSON in the first place is covered in working with JSON data in Python.
Step 8: Delete and pop keys without a KeyError
Removing a key fails the same way reading one does. del raises when the key is gone, while pop() with a default doesn’t:
cart = {"apple": 3, "cherry": 1}
try:
del cart["mango"]
except KeyError as err:
print("del failed:", err)
print(cart.pop("mango", None), cart.pop("cherry", None), cart)
tags = {"sale", "organic"}
tags.discard("new") # no error; tags.remove("new") raises KeyError
print(sorted(tags))
Output:
del failed: 'mango'
None 1 {'apple': 3}
['organic', 'sale']
pop("mango", None) returned None quietly, and cherry was removed. Sets work the same way: remove() raises KeyError and discard() doesn’t. More options are in removing a key without an error.
Where else does a Python KeyError come from?
Dictionaries aren’t the only source. Anything that behaves like a mapping raises KeyError too, and three of them catch people out often:
import os
import pandas as pd
try:
os.environ["STORE_API_KEY"]
except KeyError as err:
print("os.environ:", repr(err))
print("getenv: ", os.getenv("STORE_API_KEY", "not set"))
df = pd.DataFrame({"item": ["apple", "cherry"], "price": [1.25, 4.99]})
try:
df["Price"]
except KeyError as err:
print("pandas: ", repr(err))
print("'price' in df.columns:", "price" in df.columns, "| sum:", df["price"].sum())
try:
{"sale", "organic"}.remove("new")
except KeyError as err:
print("set.remove:", repr(err))
Output:
os.environ: KeyError('STORE_API_KEY')
getenv: not set
pandas: KeyError('Price')
'price' in df.columns: True | sum: 6.24
set.remove: KeyError('new')
Environment variables are a mapping, so os.getenv() is the safe read. I cover that in environment variables in Python.
pandas raises KeyError for a column name with the wrong case, like 'Price'. Check whether a pandas column exists first.
Which KeyError fix should you use?
Keep this cheat sheet handy when you review code. Every row is something we ran above on the same price list:
| Situation | Use | Missing key gives |
|---|---|---|
| Missing key needs its own branch | if key in d: | Your else branch |
| Missing key means a default | d.get(key, default) | The default (None if omitted) |
| Miss is rare and worth logging | try / except KeyError | Your except block |
| Counting or grouping | defaultdict(int) or setdefault() | A new key with the default |
| Default without storing it | dict subclass with __missing__ | The returned value |
| Nested JSON | Chained get() or a dig() helper | The default |
| Removing a key | d.pop(key, None) | None |
| Environment variable | os.getenv(name, default) | The default |
Common Mistakes to Avoid with KeyError
- Wrapping a whole block in
try. A KeyError from an unrelated line gets swallowed too. Keep the lookup alone in thetryand the rest inelse. - Trusting
get()whenNoneis a stored value. As Step 4 showed, the default never applies. Test withinwhen that matters. - Reading a
defaultdictto check it. The read inserts the key. Usekey in dto check. - Silencing a key that should exist. A default hides a typo like
'Banana'. Fix the data, as in Step 2, instead.
Is KeyError the same as IndexError in Python?
No, but they’re close cousins. Both inherit from LookupError: KeyError is for mappings such as dictionaries, while IndexError is for positions in sequences like lists and strings.
So except LookupError catches either one, which is handy in the dig() helper above. For the list side, see fixing IndexError: list index out of range.
Next Steps after fixing the KeyError
Once lookups are safe, the next thing you’ll want to tidy is how your dictionary gets changed. Our guide to updating values in a Python dictionary picks up from here.
A few more dictionary guides our team keeps coming back to:
- Convert a string to a dictionary in Python
- Count the keys in a Python dictionary
- Remove multiple keys from a dictionary
- Write a dictionary to a file in Python
Frequently asked questions
What causes a KeyError in a Python dictionary?
Reading d[key] or running del d[key] when the key isn’t in the dictionary. Typos, different case, extra spaces and int vs str keys are the usual reasons.
How do I avoid KeyError when a key might be missing?
Use d.get(key, default) for a default, if key in d for a separate branch, or try / except KeyError when a miss is rare.
Does dict.get() ever raise KeyError?
No. It returns None or your default. If the key exists with a stored value of None, you get None, not the default.
How do I get the missing key from a KeyError?
Catch it as err and read err.args[0]. str(err) wraps the key in quotes.
Why does a defaultdict never raise KeyError?
It calls its default factory, such as int, for a missing key and stores the result. That also means reading a missing key adds it.
Where is KeyError documented?
See KeyError in the Python built-in exceptions docs.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn