One of the most common errors Python beginners encounter is:
IndexError: list index out of range
This error is frustrating because it crashes your program, but the good news is that it's usually easy to fix once you understand what's happening.
What is IndexError?
An IndexError occurs when you try to access a position (index) in a list that doesn't exist. Think of it like trying to open the 10th door in a hallway that only has 5 doors - the door simply isn't there.
In Python, list indices start at 0, so a list with 5 elements has valid indices: 0, 1, 2, 3, 4.
Understanding List Indexing
Before we dive into errors, let's review how Python indexes lists:
fruits = ['apple', 'banana', 'cherry']
# Valid indices: 0, 1, 2
print(fruits[0]) # 'apple'
print(fruits[1]) # 'banana'
print(fruits[2]) # 'cherry'
# Negative indices (count from the end)
print(fruits[-1]) # 'cherry' (last item)
print(fruits[-2]) # 'banana' (second to last)
print(fruits[-3]) # 'apple' (third to last)
# Invalid indices
print(fruits[3]) # IndexError! Only indices 0-2 exist
print(fruits[-4]) # IndexError! Only -3 to -1 exist
Key Point: If a list has n elements, valid positive indices are 0 to n-1, and valid negative indices are -n to -1.
Common Causes and Solutions
Cause 1: Off-by-One Error
The most common mistake - forgetting that lists start at index 0.
names = ['Alice', 'Bob', 'Charlie']
# WRONG: Trying to get the "3rd element" at index 3
print(names[3])
# IndexError: list index out of range
Why it fails: The list has 3 elements (indices 0, 1, 2). There is no index 3.
Fix:
# CORRECT: The "3rd element" is at index 2
print(names[2]) # 'Charlie'
# Or use negative indexing for last element
print(names[-1]) # 'Charlie'
Cause 2: Looping with Wrong Range
Using the length of the list directly as a loop limit.
numbers = [10, 20, 30, 40]
# WRONG: range(4) gives 0, 1, 2, 3 - the last index is 4, which doesn't exist
for i in range(len(numbers)):
print(numbers[i]) # Works until i=3
print(numbers[i + 1]) # IndexError when i=3!
Fix Option 1: Adjust your range
# CORRECT: Stop one element earlier
for i in range(len(numbers) - 1):
print(numbers[i])
print(numbers[i + 1])
Fix Option 2: Use enumerate() (BETTER)
# BETTER: Iterate over the list directly
for i, num in enumerate(numbers):
print(f"Index {i}: {num}")
Fix Option 3: Use zip() for pairs
# BEST for accessing adjacent elements
for current, next_item in zip(numbers, numbers[1:]):
print(f"Current: {current}, Next: {next_item}")
Cause 3: Empty List
Trying to access any index of an empty list.
empty_list = []
# WRONG: No elements at all!
first_item = empty_list[0]
# IndexError: list index out of range
Fix: Check if list is empty first
# CORRECT: Check before accessing
if empty_list:
first_item = empty_list[0]
else:
first_item = None # or handle appropriately
# Or use conditional expression
first_item = empty_list[0] if empty_list else None
# Or use try/except
try:
first_item = empty_list[0]
except IndexError:
first_item = None
Cause 4: List Modified During Iteration
Removing items from a list while looping through it.
numbers = [1, 2, 3, 4, 5]
# WRONG: Don't modify list while iterating by index
for i in range(len(numbers)):
if numbers[i] % 2 == 0:
numbers.pop(i) # This changes the list size!
# IndexError on later iterations
Why it fails: When you remove an element, the list becomes shorter, but your loop still tries to access the original indices.
Fix Option 1: Iterate backwards
# CORRECT: Go backwards to avoid index shifting issues
for i in range(len(numbers) - 1, -1, -1):
if numbers[i] % 2 == 0:
numbers.pop(i)
Fix Option 2: List comprehension (BEST)
# BETTER: Create new list with only odd numbers
numbers = [num for num in numbers if num % 2 != 0]
Fix Option 3: Create a copy
# CORRECT: Iterate over a copy
for num in numbers[:]: # [:] creates a copy
if num % 2 == 0:
numbers.remove(num)
Cause 5: Incorrect Split or String Operations
Working with split strings and assuming indices exist.
# User input might not have the expected format
user_input = "John" # Expected: "FirstName LastName"
# WRONG: Assumes there's always a space and two parts
first_name, last_name = user_input.split()
# ValueError: not enough values to unpack (expected 2, got 1)
# Or accessing by index
parts = user_input.split()
last_name = parts[1] # IndexError if only one word!
Fix: Validate the data first
# CORRECT: Check before unpacking
parts = user_input.split()
if len(parts) >= 2:
first_name = parts[0]
last_name = parts[1]
else:
first_name = parts[0] if parts else "Unknown"
last_name = "Unknown"
# Or use defaults
parts = user_input.split() + ['Unknown'] * 2
first_name, last_name = parts[0], parts[1]
Cause 6: Accessing Nested Lists
Trying to access indices in multi-dimensional lists without checking dimensions.
matrix = [
[1, 2, 3],
[4, 5, 6]
]
# WRONG: Assumes all rows have same length
print(matrix[0][3]) # IndexError: row 0 only has indices 0-2
# WRONG: Assumes more rows exist
print(matrix[2][0]) # IndexError: only 2 rows (indices 0-1)
Fix: Validate dimensions
# CORRECT: Check both dimensions
row, col = 0, 3
if row < len(matrix) and col < len(matrix[row]):
print(matrix[row][col])
else:
print("Index out of bounds")
Cause 7: File or Input Processing
Reading lines from a file and assuming a certain number of lines.
# WRONG: Assumes file has at least 10 lines
with open('data.txt', 'r') as f:
lines = f.readlines()
header = lines[0]
data = lines[1:10] # What if file has fewer than 10 lines?
Fix: Check length first
# CORRECT
with open('data.txt', 'r') as f:
lines = f.readlines()
if len(lines) > 0:
header = lines[0]
data = lines[1:min(10, len(lines))]
else:
print("File is empty")
Defensive Programming Strategies
Strategy 1: Always Validate Indices
def safe_get(lst, index, default=None):
"""Safely get an item from a list."""
try:
return lst[index]
except IndexError:
return default
# Usage
numbers = [1, 2, 3]
print(safe_get(numbers, 5, "Not found")) # "Not found"
print(safe_get(numbers, 1)) # 2
Strategy 2: Use Bounds Checking
def get_item(lst, index):
"""Get item with bounds checking."""
if -len(lst) <= index < len(lst):
return lst[index]
else:
raise ValueError(f"Index {index} out of range for list of length {len(lst)}")
# Or without exception
def get_item_safe(lst, index):
if -len(lst) <= index < len(lst):
return lst[index]
return None
Strategy 3: Prefer Iteration Over Indexing
# Instead of this:
for i in range(len(my_list)):
process(my_list[i])
# Do this:
for item in my_list:
process(item)
# If you need the index too:
for i, item in enumerate(my_list):
print(f"Item {i}: {item}")
Strategy 4: Use Slicing (It Never Raises IndexError)
my_list = [1, 2, 3]
# Indexing raises error
print(my_list[10]) # IndexError
# Slicing returns empty list or partial results
print(my_list[10:]) # []
print(my_list[:10]) # [1, 2, 3]
print(my_list[1:10]) # [2, 3]
Debugging Techniques
When you get an IndexError, here's how to debug it:
1. Check the Error Message
Traceback (most recent call last):
File "script.py", line 15, in <module>
print(data[i])
IndexError: list index out of range
The line number (15) tells you where the error occurred.
2. Print List Length and Index
# Add debug prints
print(f"List length: {len(my_list)}")
print(f"Trying to access index: {i}")
print(f"List contents: {my_list}")
3. Use Assertions During Development
def process_items(items, start_index):
assert 0 <= start_index < len(items), \
f"start_index {start_index} out of range for list of length {len(items)}"
return items[start_index]
4. Use Try/Except for Unknown Inputs
def get_user_selection(options, user_input):
try:
index = int(user_input)
return options[index]
except (ValueError, IndexError):
return "Invalid selection"
Real-World Example: CSV Processing
Here's a complete example showing best practices:
import csv
def process_csv(filename):
"""Process CSV file with proper error handling."""
try:
with open(filename, 'r') as f:
reader = csv.reader(f)
# Get header (first row)
try:
header = next(reader)
except StopIteration:
print("Error: CSV file is empty")
return
# Process data rows
for row_num, row in enumerate(reader, start=2):
# Validate row has expected number of columns
if len(row) < 3:
print(f"Warning: Row {row_num} has fewer than 3 columns, skipping")
continue
# Safe access with bounds checking
name = row[0] if len(row) > 0 else "Unknown"
age = row[1] if len(row) > 1 else "N/A"
city = row[2] if len(row) > 2 else "N/A"
print(f"{name}, {age}, {city}")
except FileNotFoundError:
print(f"Error: File {filename} not found")
Summary
IndexError occurs when you try to access a list index that doesn't exist. The most common causes:
- Off-by-one errors - Forgetting indices start at 0
- Wrong loop ranges - Using
range(len(list))carelessly - Empty lists - Not checking if list has elements
- Modified lists - Changing list size during iteration
- Invalid assumptions - Expecting data to have a certain structure
Prevention strategies:
- ✅ Check list length before accessing:
if len(lst) > index - ✅ Use
enumerate()instead of manual indexing - ✅ Prefer iteration over indexing when possible
- ✅ Use slicing (never raises IndexError)
- ✅ Validate input data structure
- ✅ Use try/except for user input or unknown data
Remember: Python's IndexError is helpful - it's telling you that your assumptions about the data structure are wrong. When you see it, check your list length and index values!
Comments
Sign in to join the conversation