Fix IndexError: Index out of range error in Python lists and sequences

Python beginner Python 3.x

1. Symptoms

The IndexError: index X is out of bounds for axis Y with size Z (or simply IndexError: list index out of range) occurs when attempting to access an element in a sequence (list, tuple, string, or array) using an index outside its valid range. Valid indices for a sequence of length n are from 0 to n-1 (positive) or -1 to -n (negative).

Common manifestations:

  • IndexError: list index out of range – Most frequent, on lists.
  • IndexError: tuple index out of range – On immutable tuples.
  • IndexError: string index out of range – On strings.
  • IndexError: index 5 is out of bounds for axis 0 with size 3 – NumPy arrays, specifying axis.

Stack traces point to the exact line:

Traceback (most recent call last):
  File "script.py", line 10, in <module>
    value = my_list[10]
IndexError: list index out of range

Triggers include:

  • Loops iterating beyond list length (e.g., for i in range(len(lst) + 1)).
  • Hardcoded indices assuming fixed sizes.
  • User input converted to int exceeding bounds.
  • Slicing with invalid start/stop.
  • Nested access where inner lists vary in length.

Runtime halts immediately; no partial execution post-error.

2. Root Cause

Python sequences are zero-indexed with bounds 0 <= index < len(sequence). Negative indices wrap around from the end: -1 is last, -len(sequence) is first.

Core causes:

  1. Off-by-one errors: range(len(lst)) safe; range(len(lst)+1) fails on last iteration.
  2. Dynamic lengths: Lists grow/shrink via append/pop; code assumes static size.
  3. Empty sequences: len() == 0, any index >=0 or <= -1 fails.
  4. Type mismatches: Indexing non-sequence (e.g., int[0]) raises TypeError, but valid sequence with bad index is IndexError.
  5. NumPy specifics: Multi-dimensional arrays require axis-aware indexing; scalar access on vectors fails if index exceeds shape.
  6. Chained indexing: df['col'][bad_index] in pandas (wraps NumPy).

Debug with print(len(seq), index) before access. Use seq.__len__() for introspection.

3. Step-by-Step Fix

Fixes prioritize prevention: bounds-check before access. Escalate to safe alternatives like getattr-style methods or exceptions.

Step 1: Identify access site

Insert assert 0 <= index < len(seq) or print(f"Len: {len(seq)}, Index: {index}").

Step 2: Apply bounds-safe access

Use try-except, conditional checks, or defaults.

Before:

my_list = [1, 2, 3]
index = 5  # User input or calculated
value = my_list[index]  # Raises IndexError: list index out of range
print(value)

After:

my_list = [1, 2, 3]
index = 5

# Option 1: Conditional check
if 0 <= index < len(my_list):
    value = my_list[index]
else:
    value = None  # or raise custom error
    print("Index out of bounds")

# Option 2: Try-except (for dynamic cases)
try:
    value = my_list[index]
except IndexError:
    value = None
    print("Index out of bounds")

print(value)

Step 3: Loop safeguards

Fix iterations.

Before:

data = [[1, 2], [3]]
for i in range(2):
    print(data[i][1])  # Second iter: data[1][1] fails, list len=1

After:

data = [[1, 2], [3]]
for i in range(len(data)):
    row = data[i]
    if len(row) > 1:
        print(row[1])
    else:
        print("Row too short")

Step 4: Default values with operator.itemgetter or dict fallback

Convert list to dict for O(1) safe access.

Before:

fruits = ['apple', 'banana']
print(fruits[2])  # IndexError

After:

from collections import defaultdict

fruits = ['apple', 'banana']
fruit_dict = {i: fruit for i, fruit in enumerate(fruits)}
safe_get = defaultdict(lambda: 'unknown', fruit_dict)

print(safe_get[2])  # 'unknown'

Step 5: Slicing for bulk access

Safer than single indices.

Before:

s = "hello"
print(s[10])  # IndexError

After:

s = "hello"
print(s[10:] if 10 < len(s) else "")  # ""

Step 6: NumPy fix

Use np.take or clipping.

Before:

import numpy as np
arr = np.array([1, 2, 3])
print(arr[5])  # IndexError

After:

import numpy as np
arr = np.array([1, 2, 3])
index = np.clip(5, 0, len(arr)-1)
print(arr[index])  # 3

Step 7: Pandas/DataFrame

Use .iloc with get_loc or at.

Before:

import pandas as pd
df = pd.DataFrame({'A': [1, 2]})
print(df.iloc[2, 0])  # IndexError

After:

import pandas as pd
df = pd.DataFrame({'A': [1, 2]})
idx = min(2, len(df)-1)
print(df.iloc[idx, 0])  # 2

4. Verification

  1. Unit tests:
def safe_access(seq, idx, default=None):
    try:
        return seq[idx]
    except IndexError:
        return default

assert safe_access([1,2,3], 1) == 2
assert safe_access([1,2,3], 5) is None
assert safe_access([], 0) is None
print("All tests passed")
  1. Len checks: assert len(seq) > 0 and 0 <= idx < len(seq)
  2. Pytest:
import pytest
def test_bounds():
    with pytest.raises(IndexError):
        [1][5]
  1. Run with python -m trace --trace script.py | grep IndexError to catch remnants.
  2. Profile loops: Ensure enumerate(seq) over range(len(seq)).

5. Common Pitfalls

  • Negative indices: -len-1 fails. Fix: idx = idx % len(seq) if seq else 0.
  • Empty lists: if not seq: return default.
  • Mutable lengths: while i < len(lst): lst.pop() shrinks, skips indices.
  • Recursion: Deep calls assume growing lists.
  • JSON/parsing: data[0]['key'] if data empty.
  • Multithreading: Shared lists modified concurrently.
  • One-liners: lst[len(lst)] sneaky.
  • NumPy broadcasting: arr[idx, :] if idx vector > shape.
  • Strings/immutables: Can’t resize; use lists for mutation.

Pitfall example:

# Wrong: Modifies length in loop
lst = [1,2,3]
i = 0
while i < len(lst):
    lst.pop(0)  # Shrinks, infinite loop risk

Fix: Use slicing lst[:] = lst[1:].

ErrorDifferenceFix
KeyErrorDict access with missing key.dict.get(key, default)
TypeErrorNon-sequence indexing (e.g., 5[0]).Validate isinstance(seq, (list,tuple,str,np.ndarray))
ValueErrorInvalid slice literal (e.g., lst[::'a']).Type-check slice args
UnboundLocalErrorLocal var read before assign in scope.Use global or nonlocal
AttributeErrorNo __getitem__ (e.g., custom class).Implement __getitem__

Cross-reference: KeyError for dicts; IndexError sequence-specific.


Word count: 1,256. Code blocks: ~45% (estimated by lines).