KeyError in Python: How to Fix a Dictionary KeyError

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'
Command Prompt traceback of a Python dictionary KeyError: 'mango' raised inside a loop that totals an order
Apple and cherry print fine; the third item stops the loop with 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
Command Prompt output listing dictionary keys with repr and difflib close matches for keys that cause KeyError
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'
Command Prompt output of Python catching a dictionary KeyError with try except, logging a warning and printing the order total
The missing mango becomes a logged warning, the total still prints, and 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}
Command Prompt output showing a Python KeyError when counting with a plain dict, then setdefault and defaultdict counting correctly
The plain dict fails on the first new fruit; 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
Command Prompt output of a KeyError on a missing nested JSON key, fixed with chained get() calls and a small dig() helper
The city reads fine, the missing 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:

SituationUseMissing key gives
Missing key needs its own branchif key in d:Your else branch
Missing key means a defaultd.get(key, default)The default (None if omitted)
Miss is rare and worth loggingtry / except KeyErrorYour except block
Counting or groupingdefaultdict(int) or setdefault()A new key with the default
Default without storing itdict subclass with __missing__The returned value
Nested JSONChained get() or a dig() helperThe default
Removing a keyd.pop(key, None)None
Environment variableos.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 the try and the rest in else.
  • Trusting get() when None is a stored value. As Step 4 showed, the default never applies. Test with in when that matters.
  • Reading a defaultdict to check it. The read inserts the key. Use key in d to 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:

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.