pythonseverity: can-fix
KeyError

Python KeyError: 'X' - dictionary key not found

KeyError: 'X' - missing dictionary key

99% fixable~3 mindifficulty: beginner

Verified against CPython 3.13 source (Objects/dictobject.c), Python docs: built-in-exceptions, PEP 20: Zen of Python · Updated April 2026

> quick_fix

You accessed a dict key that doesn't exist. Use d.get('key') to return None instead of raising, d.get('key', default) to return a fallback, or check with 'key in d' before subscripting.

# Raises KeyError
name = user['name']

# Fix 1 - get with default
name = user.get('name', 'Anonymous')

# Fix 2 - membership check
if 'name' in user:
    name = user['name']
else:
    name = 'Anonymous'

# Fix 3 - try-except
try:
    name = user['name']
except KeyError:
    name = 'Anonymous'

What causes this error

Python dicts raise KeyError on subscript access when the key is not present. Unlike JavaScript objects (which return undefined), Python is explicit - missing keys are an exception, not a silent None. The exception's argument is the missing key itself, which makes catching specific keys easy.

> advertisementAdSense placeholder

How to fix it

  1. 01

    step 1

    Identify which key is missing

    The error message includes the key name in quotes: KeyError: 'email'. That's the key your code expected but the dict didn't have.

  2. 02

    step 2

    Check the dict's actual contents

    Add a print(d.keys()) above the failing line. If the key is misspelled, you'll see the typo. If it's truly missing, decide whether to fill a default or raise.

  3. 03

    step 3

    Use .get() with a sensible default

    d.get('key', default_value) returns default_value if the key is missing. Cleanest for optional fields.

  4. 04

    step 4

    For nested dicts, use chained .get()

    d.get('user', {}).get('name', 'Anonymous') - safe even if 'user' key is missing entirely.

    # Bad - KeyError on missing 'user'
    name = data['user']['name']
    
    # Good - safe chained get
    name = data.get('user', {}).get('name', 'Anonymous')
  5. 05

    step 5

    For dicts where missing keys are normal, use defaultdict

    from collections import defaultdict; d = defaultdict(list) - d['anykey'] auto-creates an empty list, no KeyError ever.

How to verify the fix

  • The KeyError no longer fires.
  • Missing-key case returns the expected default.
  • Print d.keys() shows the keys you expect.

Why KeyError happens at the runtime level

Python's dict type implements the mp_subscript slot via dict_subscript in Objects/dictobject.c. When you write d[k], the interpreter computes hash(k), looks up the bucket, walks the collision chain, and either returns the value or calls __missing__ if the type defines it. Plain dicts don't define __missing__, so they fall through to PyErr_SetObject(PyExc_KeyError, key), raising KeyError with the missing key as the argument. The check is unconditional - the dict has no fallback path for missing keys unless you subclass and override __missing__ (which is how defaultdict works).

Common debug mistakes for KeyError

  • Iterating dict.values() expecting keys - the loop runs over values, then indexing back into the dict with the value raises KeyError when the value isn't itself a key.
  • Reading os.environ['MY_VAR'] without a default - if the env var isn't set, KeyError fires at startup; use os.environ.get('MY_VAR', 'default') instead.
  • Catching except: pass to silence KeyError without distinguishing from other exceptions - swallows real bugs along with the missing-key case.
  • Assuming json.loads always returns the same shape - the JSON might have an optional field that's sometimes missing; access it via .get().
  • Modifying a dict during iteration and continuing to access the deleted key - the for loop has cached keys and the deleted one raises KeyError when you index into d on the next iteration.

When KeyError signals a deeper problem

Persistent KeyError across a codebase usually means dict-shaped data is being passed around without a schema. When every function takes 'data: dict' as a parameter, every function has to defensively .get() every key it needs and the type system has no way to enforce structure. The architectural fix is to convert dict-shaped boundary data into typed objects (dataclass, Pydantic model, attrs class) at the entry point, so internal code can access fields directly without KeyError handling. Without this, every API change risks a KeyError in a deeply-nested handler and the bug only surfaces with specific input shapes.

Frequently asked questions

What's the difference between KeyError and IndexError?

KeyError - missing key in a mapping (dict, set). IndexError - missing position in a sequence (list, tuple, string). Different types but same conceptual error.

Why doesn't Python return None for missing keys like JavaScript?

Explicit is better than implicit (PEP 20). Returning None would let bugs propagate silently; raising forces the developer to handle the case.

Is .get() slower than direct indexing?

Negligibly - both are O(1) hash lookups. The function call overhead of .get() is microseconds; only matters in tight inner loops over millions of items.

disclosure:Errordex runs AdSense, has zero third-party affiliate or sponsored links, and occasionally links to the editor’s own paid digital products (clearly labelled). Every fix is manually verified against official sources listed in the “sources” sidebar. If a fix here didn’t work for you, please email so we can update the page.