Skip to content

Fix: Python IndexError: list index out of range

FixDevs ·

Quick Answer

How to fix Python IndexError list index out of range caused by empty lists, off-by-one errors, wrong loop bounds, deleted elements, and negative indexing mistakes.

The Error

You run a Python script and get:

Traceback (most recent call last):
  File "app.py", line 4, in <module>
    print(items[5])
IndexError: list index out of range

Or variations:

IndexError: string index out of range
IndexError: tuple index out of range

You tried to access an element at an index that does not exist. The list (or string, or tuple) does not have that many elements.

Why This Happens

Python sequences are zero-indexed. A list with 5 elements has valid indices 0 through 4. Accessing index 5 is out of range:

items = ["a", "b", "c", "d", "e"]  # len = 5
items[0]   # "a" — first element
items[4]   # "e" — last element
items[5]   # IndexError — no 6th element

Common causes:

  • Empty list. The list has no elements, so any index fails.
  • Off-by-one error. Using len(items) as an index instead of len(items) - 1.
  • Loop modifying the list. Removing elements while iterating changes the list length.
  • Hardcoded index. Assuming data always has a certain number of elements.
  • API response with fewer items than expected. The response returned 2 items but you access response[3].

Fix 1: Check the List Length First

Before accessing an index, verify the list has enough elements:

items = get_results()

if len(items) > 0:
    first = items[0]
else:
    first = None

For a specific index:

if len(items) > 3:
    fourth = items[3]

Using a helper function:

def safe_get(lst, index, default=None):
    return lst[index] if -len(lst) <= index < len(lst) else default

items = [10, 20, 30]
safe_get(items, 5)        # None
safe_get(items, 5, 0)     # 0
safe_get(items, 1)        # 20

Pro Tip: For dictionaries, use .get() with a default value. Lists do not have .get(), so you need the length check or try/except. If you frequently need safe list access, consider a helper function or using next(iter(items), default) for the first element.

Fix 2: Fix Off-By-One Errors

The most common logic error. Lists are zero-indexed, so the last valid index is len(items) - 1:

Broken:

items = [10, 20, 30]

# Wrong — index 3 does not exist
for i in range(len(items) + 1):
    print(items[i])  # IndexError when i = 3

Fixed:

for i in range(len(items)):
    print(items[i])

Better — iterate directly:

for item in items:
    print(item)

Access the last element safely:

items = [10, 20, 30]
last = items[-1]         # 30 — negative indexing
last = items[len(items) - 1]  # 30 — manual (avoid this)

Negative indices count from the end: -1 is the last element, -2 is second to last. But items[-1] still raises IndexError on an empty list.

Fix 3: Handle Empty Lists

Empty lists cause IndexError on any index access:

results = []
first = results[0]  # IndexError: list index out of range

Fix with a guard:

results = get_search_results(query)

if results:
    first = results[0]
    print(f"Top result: {first}")
else:
    print("No results found")

Fix with a default value:

first = results[0] if results else "No results"

Fix with try/except:

try:
    first = results[0]
except IndexError:
    first = None

The if results: check is cleanest for most cases. Use try/except when the empty case is genuinely exceptional.

Fix 4: Fix Loops That Modify Lists

Removing elements while iterating over a list changes the indices:

Broken:

items = [1, 2, 3, 4, 5]

for i in range(len(items)):
    if items[i] % 2 == 0:
        items.pop(i)  # List shrinks, but range doesn't
# IndexError: list index out of range

When you pop index 1 (value 2), the list becomes [1, 3, 4, 5]. Now index 3 is 5, and index 4 does not exist.

Fixed — iterate over a copy:

items = [1, 2, 3, 4, 5]
items = [x for x in items if x % 2 != 0]
# [1, 3, 5]

Fixed — iterate in reverse:

items = [1, 2, 3, 4, 5]
for i in range(len(items) - 1, -1, -1):
    if items[i] % 2 == 0:
        items.pop(i)

Iterating in reverse is safe because popping later indices does not affect earlier ones.

Fixed — filter:

items = list(filter(lambda x: x % 2 != 0, items))

Common Mistake: Using for item in items: items.remove(item) to remove elements. This skips elements because remove() shifts the remaining elements and the loop’s internal pointer advances past the next item. Always use list comprehension or reverse iteration.

Fix 5: Fix String Indexing

Strings are sequences too, and the same rules apply:

name = "Alice"
name[0]   # "A"
name[4]   # "e"
name[5]   # IndexError: string index out of range

Common case — split with fewer parts than expected:

line = "Alice"
parts = line.split(",")  # ["Alice"] — only 1 part
name = parts[0]           # "Alice"
email = parts[1]          # IndexError!

Fixed:

parts = line.split(",")
name = parts[0] if len(parts) > 0 else ""
email = parts[1] if len(parts) > 1 else ""

Or with unpacking and defaults:

parts = line.split(",")
name, *rest = parts
email = rest[0] if rest else ""

If your string parsing involves JSON, see Fix: JSON parse unexpected token for format issues.

Fix 6: Fix Pandas and NumPy Indexing

Pandas DataFrames and NumPy arrays can also raise IndexError:

Pandas — iloc out of range:

import pandas as pd

df = pd.DataFrame({"name": ["Alice", "Bob"]})
df.iloc[5]  # IndexError: single positional indexer is out-of-bounds

Fixed:

if len(df) > 5:
    row = df.iloc[5]

NumPy:

import numpy as np

arr = np.array([1, 2, 3])
arr[5]  # IndexError: index 5 is out of bounds for axis 0 with size 3

Fix: Check shape before accessing:

if arr.shape[0] > 5:
    value = arr[5]

Fix 7: Fix Multithreaded List Access

Multiple threads accessing the same list can cause IndexError if one thread removes elements while another accesses them:

import threading

shared_list = [1, 2, 3, 4, 5]

def worker():
    while shared_list:
        item = shared_list.pop(0)  # Race condition!
        process(item)

Fixed — use a thread-safe queue:

from queue import Queue

q = Queue()
for item in [1, 2, 3, 4, 5]:
    q.put(item)

def worker():
    while not q.empty():
        item = q.get()
        process(item)
        q.task_done()

Or use a lock:

lock = threading.Lock()

def worker():
    with lock:
        if shared_list:
            item = shared_list.pop(0)
            process(item)

Fix 8: Debug the Index Error

When the error is not obvious, add debugging:

items = get_data()
index = calculate_index()

print(f"List length: {len(items)}")
print(f"Attempting index: {index}")
print(f"List contents: {items}")

value = items[index]

Use enumerate for safe indexed access:

for i, item in enumerate(items):
    print(f"Index {i}: {item}")

This never raises IndexError because enumerate only yields valid indices.

Still Not Working?

If you have checked all the fixes above:

Check for nested lists. items[0][1] fails if items[0] has fewer than 2 elements, even if items has many elements.

Check for generators vs lists. Generators cannot be indexed:

gen = (x for x in range(10))
gen[0]  # TypeError: 'generator' object is not subscriptable

Convert to list first: list(gen)[0].

Check for deque maxlen. A collections.deque with maxlen automatically removes old elements, which might make expected indices invalid.

Check for custom __getitem__. If the object is a custom class, its __getitem__ method might raise IndexError for unexpected reasons.

If the error is about dictionary keys rather than list indices, see Fix: Python KeyError. If the value is None and you are trying to subscript it, see Fix: TypeError: ‘NoneType’ object is not subscriptable.

For similar issues with missing attributes on None values, see Fix: AttributeError: ‘NoneType’ has no attribute.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles