PythonMastery
reference 3 min read · lesson 12 of 45 in Errors

IndexError: list index out of range

1 · The lesson

read

What this error means

You asked Python for an item at a position that doesn't exist in the list. Lists in Python are zero-indexed: a list of length 3 has valid indices 0, 1, 2. Asking for index 3 (or -4) raises IndexError.

When you see it

python
>>> nums = [10, 20, 30]
>>> nums[5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

Why it happens

The most frequent causes are off-by-one errors in loops, assumptions about input size that don't hold at runtime (empty list, short list), and confusing length with maximum index. Remember: for a list of length n, the last valid index is n - 1.

How to fix it

1. Validate the index before access:

python
i = 5
if i < len(nums):
    value = nums[i]
else:
    value = None
+ setup added so this can run · defines nums
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

nums = _AutoMock('nums')

2. Iterate the list directly instead of by index (the Pythonic fix):

python
for n in nums:
    print(n)
+ setup added so this can run · defines nums
nums = ["alpha", "beta", "gamma"]

If you need the index too, use enumerate:

python
for i, n in enumerate(nums):
    print(i, n)
+ setup added so this can run · defines nums
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

nums = _AutoMock('nums')

3. Catch the error when the bounds are genuinely uncertain:

python
try:
    head = parts[3]
except IndexError:
    head = ""
+ setup added so this can run · defines parts
parts = ["alpha", "beta", "gamma"]

4. Use slicing — slicing never raises IndexError:

python
first_five = nums[:5]   # returns [10, 20, 30] safely
+ setup added so this can run · defines nums
nums = ["alpha", "beta", "gamma"]

When you'd actually see this in real code

  • A classic off-by-one: for i in range(len(nums) + 1): nums[i] — the + 1 walks one past the end. Use range(len(nums)) or just iterate directly.
  • Splitting a CSV row and assuming five columns: row = line.split(",") then row[4] breaks on a malformed line with four commas missing or an empty trailing line in the file.
  • Popping from a list in a loop without checking emptiness: while True: x = stack.pop() should be while stack: x = stack.pop().

See Also

Practice this

on practicepython.in

Short exercises that run in your browser and tell you what your code actually did, not just whether a test passed.