KeyError when the key looks present (tested)

If print(d) shows the key sitting right there and d[key] still raises KeyError, the key you're looking up isn't actually equal to the key that's stored — even though they look identical when printed. Dict lookup uses == and hash(), not a visual match, so two values that render the same text can still be different objects. We reproduced five distinct ways this happens.

1. Five reproduced causes, same visual trap

Each of these prints as if the key is there, then raises anyway:

CodeExact error (Python 3.13.5)
d = {1: 'a'}; d['1'] (int key, str lookup)KeyError: '1'
d[lines[0]] where lines[0] is 'name\n' from readlines()KeyError: 'name\n'
d = {'Name': 1}; d['name'] (case mismatch)KeyError: 'name'
snapshot['b'] where snapshot was copied before 'b' was added to the originalKeyError: 'b'
d[nfd] where d's key is NFC-normalized and nfd is the same text NFD-normalizedKeyError: 'café'

In every case the printed representation of the two keys is indistinguishable. The fix is always to find the invisible difference, not to assume the key is simply missing — see Python KeyError: causes and fixes for the plain missing-key case instead.

2. Cause 1: int keys, string lookup (or vice versa)

1 and '1' print differently if you look closely, but it's easy to miss in a wall of output, and JSON makes it worse: json.loads() always produces string keys, even for numeric-looking ones.

>>> d = {1: 'a', 2: 'b'}
>>> d['1']
Traceback (most recent call last):
  ...
KeyError: '1'
>>> import json
>>> d = json.loads('{"1": "a"}')
>>> d
{'1': 'a'}
>>> d[1]
Traceback (most recent call last):
  ...
KeyError: 1

Fix: check type(key) against the actual keys (list(d.keys())[0].__class__), and cast explicitly — d[int(key)] or d[str(key)]. CSV rows and JSON objects are the two most common sources of this mismatch, since both can produce string keys for data that looks numeric.

3. Cause 2: trailing whitespace or newline from file reads

readlines() (and iterating a file object directly) keeps the trailing \n on every line. Printed, 'name\n' and 'name' can look the same in a terminal that just eats the newline visually.

>>> lines = open('keys.txt').readlines()
>>> lines
['name\n', 'age\n']
>>> key = lines[0]
>>> key == 'name'
False
>>> d = {'name': 'Alice', 'age': 30}
>>> d[key]
Traceback (most recent call last):
  ...
KeyError: 'name\n'

Fix: .strip() every line pulled from a file before using it as a key (or a lookup value) — key = line.strip(). If the traceback's key has a visible \n or extra spaces inside the quotes, this is the cause.

4. Cause 3: case sensitivity

Dict keys are ordinary strings underneath, and string comparison in Python is always case-sensitive — there's no special-casing for dict keys.

>>> d = {'Name': 'Alice'}
>>> d['name']
Traceback (most recent call last):
  ...
KeyError: 'name'

Fix: normalize case on the way in and out — store and look up with key.lower() consistently, or build the dict with a case-insensitive wrapper if the keys come from user input or an external API that doesn't guarantee casing.

5. Cause 4: looking at a different dict object than the one you're indexing

If a copy was taken before a mutation, or a variable got reassigned between the print() and the lookup, the two can diverge even though they share a name or started out equal.

>>> original = {'a': 1}
>>> snapshot = dict(original)
>>> original['b'] = 2
>>> print(original)
{'a': 1, 'b': 2}
>>> snapshot['b']
Traceback (most recent call last):
  ...
KeyError: 'b'

dict(original) (and .copy()) makes a shallow copy at that moment — later changes to original don't propagate to snapshot. The same confusion happens when a function parameter shadows an outer-scope variable of the same name: printing the outer one and indexing the parameter looks like the same object in the source code, but isn't. Fix: check id(d) at both the print and the lookup if you suspect this — if the ids differ, they're not the same object, no matter what the variable is named.

6. Cause 5: Unicode normalization (accented characters)

Some characters have more than one valid Unicode representation: an accented letter can be a single composed code point (NFC) or a base letter plus a separate combining accent mark (NFD). Both print identically; neither == nor dict lookup treats them as equal.

>>> import unicodedata
>>> nfc = unicodedata.normalize('NFC', 'café')
>>> nfd = unicodedata.normalize('NFD', 'café')
>>> nfc == nfd
False
>>> len(nfc), len(nfd)
(4, 5)
>>> d = {nfc: 'drink'}
>>> d[nfd]
Traceback (most recent call last):
  ...
KeyError: 'café'

This is rare but real — it shows up when keys come from different sources (one typed on macOS, which tends to produce NFD, versus one from a web form or API that sends NFC). Fix: normalize every key to the same form before using it, e.g. unicodedata.normalize('NFC', key), consistently on both the write and read side.

7. Fix checklist

Frequently asked questions

Why does KeyError happen when I can see the key in the dict?

Because dict lookup compares with == and hash(), not by how the key looks when printed. Two values can render identical text and still differ - common causes are an int/str type mismatch, a trailing newline from a file read, a case difference, indexing a different dict object than the one you printed, or (rarely) different Unicode normalization of the same accented characters.

Why does d[1] work but d['1'] raise KeyError, or the reverse?

Because 1 and '1' are different objects with different hashes - dict lookup is exact-type equality, not a loose match. This is especially common with JSON: json.loads() always produces string keys, even for fields that look numeric, so code written assuming int keys fails against JSON-sourced data.

Why does a key from readlines() not match the same text typed literally?

readlines() keeps the trailing newline character on every line except sometimes the last, so 'name\n' != 'name' even though a terminal may not visibly show the difference. Call .strip() on lines read from a file before using them as dict keys or lookup values.

Are dict keys case-sensitive in Python?

Yes, always - 'Name' and 'name' are different strings and therefore different keys, with no built-in case-insensitive mode. If keys might arrive in inconsistent casing, normalize with .lower() (or .upper()) consistently on both the write and the read side.

Can the same accented word be two different dict keys?

Yes. Unicode allows composed (NFC) and decomposed (NFD) forms of accented characters that print identically but are different code point sequences, so == and dict lookup treat them as unequal. We measured len() differing (4 vs 5) for the same visible word in NFC vs NFD. Normalize with unicodedata.normalize('NFC', key) consistently to avoid this.

Written with AI assistance. Measured on 2026-10-03 with Python 3.13.5 on Linux using plain python3 one-liners. Every traceback below is copy-pasted output, not paraphrased. PlainHeaders is our extension; Python is free.