An IndexError list index out of range is one of Python's most common runtime errors. It happens when you try to access a list element at an index that doesn't exist—asking for the 10th item when the list only has 5. While straightforward in principle, it's a beginner trap that sneaks past experienced developers too, especially in loops, conditional logic, or code handling dynamic data from APIs or databases. The error feels simple, but the cause is often buried in subtle logic, off-by-one mistakes, or assumptions about data that doesn't hold.
This guide walks you through what triggers the error, how to diagnose it from a stack trace, real patterns that cause it, and defensive strategies to prevent it entirely. We'll also cover how to safely handle it at runtime and integrate error tracking to catch it in production before users do.
What Causes IndexError: List Index Out of Range
In Python, lists are zero-indexed. The first element is at index 0, the second at 1, and so on. If a list has n elements, valid indices range from 0 to n-1.
fruits = ["apple", "banana", "cherry"]
print(fruits[0]) # "apple" — valid
print(fruits[2]) # "cherry" — valid
print(fruits[3]) # IndexError: list index out of range
The error is raised immediately when you attempt the invalid access. There are no warnings or defaults; Python doesn't return None or skip the line—it stops and raises an exception.
Negative indices work backward: fruits[-1] is "cherry", fruits[-2] is "banana". This is valid and won't raise an IndexError unless the magnitude exceeds the list length. For example, fruits[-4] will raise IndexError.
How to Read the Stack Trace
When you encounter an IndexError in development, the traceback is your map. Learning to read a stack trace is essential for finding the culprit line.
Traceback (most recent call last):
File "script.py", line 12, in <module>
value = data[index]
IndexError: list index out of range
The line value = data[index] is where the access happens. The error doesn't tell you what index was used or why it was out of bounds—you must read your code. Print or inspect index and len(data) just before the failing line, or use a debugger.
In production, this traceback should be captured and logged. Error tracking services like LightTrace help you aggregate and debug production errors so you can see how often an IndexError occurs and on which code paths.
Common Scenarios & How to Fix Them
Off-by-One Errors
The classic mistake: iterating one step too far.
# Wrong
items = [10, 20, 30]
for i in range(len(items) + 1): # 0, 1, 2, 3 — out of bounds!
print(items[i])
# Right
for i in range(len(items)): # 0, 1, 2
print(items[i])
# Better: just iterate the list directly
for item in items:
print(item)
The fix is usually to iterate over the collection, not its indices. If you need indices, use enumerate():
items = [10, 20, 30]
for idx, item in enumerate(items):
print(f"{idx}: {item}")
Iterating While Modifying
Removing or inserting elements during iteration can shift indices and cause misses or out-of-bounds access.
# Problematic
numbers = [1, 2, 3, 4, 5]
for i in range(len(numbers)):
if numbers[i] % 2 == 0:
numbers.pop(i) # Shifts remaining elements; next iteration may fail
# Fix: iterate over a copy, modify the original
numbers = [1, 2, 3, 4, 5]
for num in numbers[:]: # Iterate over a shallow copy
if num % 2 == 0:
numbers.remove(num) # Safe: modify original
# Or use list comprehension (cleaner)
numbers = [num for num in numbers if num % 2 != 0]
Empty Lists or Missing Keys in Nested Structures
Assuming a list has elements when it might be empty is another common source.
# Dangerous
data = fetch_data_from_api() # What if this returns an empty list?
first_item = data[0] # IndexError if empty
# Safe
data = fetch_data_from_api()
if data:
first_item = data[0]
else:
print("No data available")
# Or use indexing with a default
first_item = data[0] if data else None
For nested structures (lists of dicts), always check depth:
# Risky
user_name = response["users"][0]["name"] # What if "users" is empty?
# Safer
users = response.get("users", [])
if users:
user_name = users[0].get("name", "Unknown")
Prevention Strategies
Use built-in methods instead of manual indexing. Methods like first(), last(), pop(), and slicing handle edge cases gracefully:
# Instead of:
if len(items) > 0:
first = items[0]
# Use:
first = items[0] if items else None
# Or a helper
def safe_first(lst):
return lst[0] if lst else None
Validate and sanitize user input. If an index comes from a user, request, or external API, validate bounds:
def get_item(items, index):
if not isinstance(index, int) or index < 0 or index >= len(items):
raise ValueError(f"Invalid index {index} for list of length {len(items)}")
return items[index]
Use type hints and assertions during development. They won't prevent the error at runtime but clarify intent:
from typing import List
def process_records(records: List[dict]) -> str:
assert len(records) > 0, "At least one record is required"
return records[0]["id"]
Consider using a library like more-itertools or writing helper functions for safe indexing. Many teams build a small utils.py with safe_first(), safe_last(), and safe_get_index() functions to reduce boilerplate.
Catching and Handling IndexError Gracefully
Sometimes an IndexError is expected and recoverable. Catch it explicitly:
try:
item = items[index]
except IndexError:
item = default_value
print(f"Index {index} out of range; using default")
However, overly broad exception handling can hide bugs:
# Avoid this — catches other IndexErrors unintentionally
try:
item = items[index]
item.process() # If process() raises IndexError, you'll catch it
except IndexError:
item = default_value
Be specific: catch IndexError only at the line where it's expected.
Detecting and Tracking IndexError in Production
In production, an IndexError often signals a bug or unexpected input.
import sentry_sdk
sentry_sdk.init(
dsn="https://<key>@light-trace.robomiri.com/1",
traces_sample_rate=1.0
)
try:
item = items[index]
except IndexError as e:
sentry_sdk.capture_exception(e)
# Log context: what index, what list length
sentry_sdk.set_context("data", {"index": index, "list_length": len(items)})
raise # Re-raise so the caller knows
Once captured, LightTrace groups and fingerprints the errors, alerts you to new occurrences, and lets you understand your error tracking strategy across your application.
Summary
IndexError: list index out of range boils down to a mismatch between the index you're using and the list's length. The fix is almost always prevention: iterate the list directly, validate bounds before access, handle empty inputs explicitly, and avoid modifying lists during iteration. When an IndexError does occur in production, instrument it with error tracking so you catch and fix the root cause before it affects more users.
Start tracking errors in minutes
See production errors like IndexError at a glance. Start free with LightTrace—connect any Sentry SDK and get a fast, mobile-first dashboard with full stack traces, breadcrumbs, and source maps.