Python KeyError vs IndexError Explained
In Python, KeyError and IndexError are both
lookup exceptions raised when attempting to access an item that cannot
be found within a container. The primary distinction lies in the
underlying data structure: an IndexError occurs when
querying an out-of-range integer position in an ordered sequence like a
list or tuple, whereas a KeyError occurs when searching for
a non-existent key in a mapping structure like a dictionary.
Understanding the design of the data structure you are using determines
which exception Python will raise.
When Python Raises an IndexError
An IndexError is raised by sequence types—collections
where elements are ordered sequentially by integer offsets starting at
zero. These types include list, tuple,
str, bytes, and range.
An IndexError occurs in the following scenarios:
1. Indexing Beyond the Bounds of a Sequence
Attempting to access an index equal to or greater than the sequence
length, or a negative index whose absolute value exceeds the sequence
length, will raise an IndexError.
fruits = ["apple", "banana", "cherry"]
# Valid indices: 0, 1, 2 (or -3, -2, -1)
print(fruits[5]) # IndexError: list index out of range
print(fruits[-10]) # IndexError: list index out of range2. Accessing Items in an Empty Sequence
Attempting to retrieve any element by index from an empty sequence
immediately raises an IndexError.
empty_list = []
first_item = empty_list[0] # IndexError: list index out of range3. Removing Items
with pop() at an Invalid Index
Using .pop() on a list with an out-of-bounds index, or
calling .pop() on an empty list, raises an
IndexError.
items = [1, 2]
items.pop(5) # IndexError: pop index out of rangeNote: Sequence slicing (e.g., fruits[1:10])
does not raise an IndexError; it simply
returns as many items as are available within the specified range.
When Python Raises a KeyError
A KeyError is raised by mapping types—structures that
associate unique, hashable keys with values. The most common mapping
type in Python is dict. A KeyError is also
raised by set when attempting to remove an element that
does not exist.
A KeyError occurs in the following scenarios:
1. Accessing a Non-Existent Dictionary Key
When using square-bracket notation to look up a key that is not in
the dictionary, Python raises a KeyError.
user = {"name": "Alice", "age": 30}
print(user["email"]) # KeyError: 'email'2. Deleting a Non-Existent Key
Using the del keyword or .pop() on a
dictionary key that does not exist triggers a KeyError.
user = {"name": "Alice"}
del user["email"] # KeyError: 'email'
user.pop("email") # KeyError: 'email'3. Removing a Missing Element from a Set
Calling .remove() on a set with a value not
present in that set raises a KeyError (unlike
.discard(), which fails silently).
tags = {"python", "coding"}
tags.remove("javascript") # KeyError: 'javascript'The Integer Key Ambiguity
A common source of confusion occurs when a dictionary uses integers
as keys. Even if the lookup value is an integer that looks like a
sequential index, the container remains a mapping, so a failed lookup
will always raise a KeyError, never an
IndexError.
data = {0: "first", 1: "second"}
# Looking for key 5, not the 5th element:
print(data[5]) # KeyError: 5Comparison Summary
| Feature | IndexError |
KeyError |
|---|---|---|
| Applicable Types | Sequences (list,
tuple, str, bytes) |
Mappings (dict) and Sets
(set.remove()) |
| Lookup Mechanism | Positional integer offset (0
to n - 1) |
Any hashable key (strings, ints, tuples, etc.) |
| Trigger Cause | Position is outside the container boundaries | Key does not exist in the collection |
How to Prevent Both Exceptions
Preventing IndexError:
- Check length: Ensure the index is within bounds
using
if index < len(sequence):. - Use slicing: Slicing handles out-of-range bounds
safely without throwing exceptions (e.g.,
sequence[index:index+1]). - Handle explicitly: Wrap the access in a
try...except IndexErrorblock.
Preventing KeyError:
- Use
.get(): Retrieve values safely with a fallback default (e.g.,my_dict.get("key", default_value)). - Check membership: Use the
inoperator (e.g.,if "key" in my_dict:). - Use
defaultdict: Usecollections.defaultdictto automatically initialize missing keys. - Handle explicitly: Wrap the access in a
try...except KeyErrorblock.