How to fix list index out of range error in Python

Learn how to fix the 'list index out of range' error in Python. Discover solutions, tips, real-world examples, and debugging techniques.

How to fix list index out of range error in Python
Published on: 
Fri
Feb 20, 2026
Updated on: 
Mon
Apr 6, 2026
The Replit Team

The IndexError: list index out of range is a common Python error. It occurs when you try to access a list element with an index that is outside the list's boundaries.

Here, you'll find practical techniques to prevent this error. You will learn debugging tips and see real-world examples to help you write more robust code and handle list operations with confidence.

Understanding the list index out of range error

my_list = [10, 20, 30]
# This will cause an error
try:
print(my_list[3]) # Trying to access index 3 (4th element)
except IndexError as e:
print(f"Error: {e}")--OUTPUT--Error: list index out of range

This error happens because of Python's zero-based indexing. The list my_list has three elements, but the code tries to access a fourth one at index 3. Here’s the breakdown:

  • The list's length is 3.
  • Valid indices are 0, 1, and 2.
  • The code requests my_list[3], which is out of bounds.

Using a try...except IndexError block is a defensive programming technique. It allows your program to catch the error and continue running instead of crashing unexpectedly.

Basic prevention techniques

Building on the try-except approach, several straightforward techniques can help you avoid this error altogether, from simple if checks to more flexible access patterns.

Using if statements to check indices

my_list = [10, 20, 30]
index = 3

if 0 <= index < len(my_list):
print(my_list[index])
else:
print(f"Index {index} is out of range. List length is {len(my_list)}")--OUTPUT--Index 3 is out of range. List length is 3

This approach gives you direct control by checking the index before you try to use it. The condition 0 <= index < len(my_list) acts as a guard, running the else block if the index is invalid. This prevents a crash and lets you handle the issue gracefully. This check confirms two key things:

  • The index is not a negative number.
  • The index is less than the list's total length, which is found using the len() function.

Using the get() method pattern

my_list = [10, 20, 30]
index = 3

# Create a get-like function for lists
def safe_get(lst, idx, default=None):
return lst[idx] if 0 <= idx < len(lst) else default

print(safe_get(my_list, index, "Not found"))--OUTPUT--Not found

While Python lists don't have a built-in get() method like dictionaries, you can create your own. This pattern wraps the index validation logic into a reusable function, safe_get. It offers a clean way to access list elements without risking an error.

  • The function checks if the index is valid before trying to access the element.
  • If the index is out of range, it returns a specified default value instead of crashing.
  • This makes your code more predictable and easier to read than scattering if/else checks everywhere.

Using try-except blocks for graceful handling

my_list = [10, 20, 30]
indices = [1, 3, 2]

for idx in indices:
try:
print(f"Value at index {idx}: {my_list[idx]}")
except IndexError:
print(f"Index {idx} is out of bounds")--OUTPUT--Value at index 1: 20
Index 3 is out of bounds
Value at index 2: 30

This method is especially useful when processing multiple items, like in a for loop where some indices might be invalid. The try block attempts an operation that could fail. If an IndexError occurs, the except block catches it, allowing the loop to continue to the next item instead of crashing.

  • The code inside try is your "optimistic" attempt to access an element.
  • The except IndexError block is your fallback, executing only when that specific error is caught.
  • This pattern ensures your program remains stable and can gracefully handle bad data during iteration.

Advanced techniques

To handle more complex situations, you can use clever patterns like circular indexing with the % operator, safe list comprehensions, or avoid indexing altogether.

Using the modulo operator for circular indexing

my_list = [10, 20, 30]
indices = [1, 3, 5, 7]

# Wrap around indices using modulo
for idx in indices:
safe_idx = idx % len(my_list)
print(f"Original index: {idx}, Safe index: {safe_idx}, Value: {my_list[safe_idx]}")--OUTPUT--Original index: 1, Safe index: 1, Value: 20
Original index: 3, Safe index: 0, Value: 10
Original index: 5, Safe index: 2, Value: 30
Original index: 7, Safe index: 1, Value: 20

The modulo operator (%) offers a clever way to create "circular" access, ensuring an index always falls within the list's bounds. It works by calculating the remainder of a division. When you use index % len(my_list), the result is always a valid index from 0 to len(my_list) - 1.

  • This technique effectively wraps oversized indices back to the beginning of the list.
  • For example, with a list of length 3, an index of 3 becomes 0 (since 3 % 3 is 0), and an index of 7 becomes 1 (since 7 % 3 is 1).

This pattern is especially useful for cyclical tasks, like rotating through a fixed set of items or accessing list of lists safely.

Using list comprehensions with safe access

my_list = [10, 20, 30]
indices = [0, 2, 4, 5]

# Using list comprehension with conditional expression
results = [my_list[i] if i < len(my_list) else f"Index {i} out of range" for i in indices]
print(results)--OUTPUT--[10, 30, 'Index 4 out of range', 'Index 5 out of range']

List comprehensions provide a concise way to build new lists while handling potential errors. By embedding a conditional if/else expression directly within the comprehension, you can check each index before using it. This pattern lets you process a collection of indices in a single, readable line without a separate loop.

  • If an index i is valid because it's less than len(my_list), the code adds the corresponding element to the new list.
  • If the index is out of bounds, it adds a fallback value instead of raising an IndexError.

Avoiding indexing with iterators and unpacking

data = [("Alice", 25), ("Bob", 30), ("Charlie", 35)]

# Safely unpack values without indexing
for person in data:
try:
name, age = person
print(f"{name} is {age} years old")
except ValueError as e:
print(f"Couldn't unpack: {person}. Error: {e}")--OUTPUT--Alice is 25 years old
Bob is 30 years old
Charlie is 35 years old

The most Pythonic way to avoid an IndexError is often to skip indexing entirely. By iterating directly over a collection and using unpacking, you can write safer and more readable code. This pattern lets you process structured data without manually accessing elements like person[0] or person[1].

  • The for person in data: loop gives you each tuple one by one.
  • Unpacking with name, age = person automatically assigns the tuple's contents to variables.
  • This avoids the risk of an IndexError on the inner items. The try...except ValueError is used here because unpacking fails with a ValueError, not an IndexError, if the data structure is inconsistent.

Move faster with Replit

Replit is an AI-powered development platform that comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly. Instead of wrestling with environment configurations, you can focus on building your project directly in your browser.

Knowing how to handle an IndexError is a great skill, but Agent 4 helps you move from individual techniques to building complete applications. It takes your description of an app and handles the code, databases, APIs, and deployment. Instead of piecing together code, you can describe the final product you want to build:

  • A playlist rotator that uses the modulo operator (%) to cycle through a list of songs without going out of bounds.
  • A data validation script that processes lists of user records, using conditional checks to safely handle entries with missing fields.
  • A report generator that pulls data from multiple lists, using try-except blocks to gracefully manage sources with incomplete information.

Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

Beyond the basics, certain scenarios—like using negative indices or modifying a list while iterating—are common sources of this error.

Working with negative indices

Working with negative indices

Python's negative indexing is a handy feature for accessing elements from the end of a list. For example, my_list[-1] gets the last item. However, if you use a negative index that goes beyond the list's start, you'll get an IndexError.

The code below demonstrates how a valid negative index works, followed by one that triggers the error.

my_list = [10, 20, 30]
# Trying to access elements with negative indices
print(my_list[-3]) # First element
print(my_list[-4]) # This will cause an index error

The list only has three elements, making -3 the first valid negative index. The code fails at my_list[-4] because it requests an element that simply isn't there. The following code demonstrates a safe way to handle this.

my_list = [10, 20, 30]
neg_index = -4
if abs(neg_index) <= len(my_list):
print(my_list[neg_index])
else:
print(f"Negative index {neg_index} is out of range")

This solution prevents an error by checking if the negative index is valid before access. It works by comparing the absolute value of the index, found using abs(), against the list's length.

  • If abs(neg_index) is less than or equal to len(my_list), the index is safe to use.
  • This check is crucial when indices are calculated dynamically, as it ensures you don't try to access an element that doesn't exist from the end of the list.

Common iteration mistakes with range()

The range() function is powerful for iteration, but it's easy to misuse with a list's length. This common mistake can cause your loop to skip elements, particularly the first one, or even lead to an unexpected IndexError.

The following code demonstrates how an off-by-one error in range() can cause your loop to miss the very first item in the list.

my_list = [10, 20, 30]
# Incorrect use of range with list length
for i in range(1, len(my_list)):
print(f"Processing item {i}: {my_list[i]}")
# First item is skipped!

By starting the loop with range(1, len(my_list)), you're telling Python to begin at index 1. This causes it to miss the first element at index 0. The corrected code below shows the proper way to iterate.

my_list = [10, 20, 30]
for i in range(len(my_list)):
print(f"Processing item {i}: {my_list[i]}")
# All items are processed

The corrected loop uses range(len(my_list)), which correctly generates indices from 0 up to, but not including, the list's length. This ensures every element is processed, fixing the off-by-one error where the first item was skipped.

  • This mistake often happens when you manually set the start and end points in range().
  • Always double-check your loop's starting index to avoid accidentally skipping data, especially when the first element is crucial.

List modification while iterating

Modifying a list while iterating over it is a classic pitfall. When you remove an element, the list’s size shrinks and indices shift. This can cause your loop to skip items or crash with an IndexError. The following code demonstrates this unstable behavior.

numbers = [1, 2, 3, 4, 5]
for i in range(len(numbers)):
if numbers[i] % 2 == 0:
numbers.pop(i) # This changes list size and shifts indices!
print(numbers) # Will likely cause IndexError

The loop's range is calculated once. As pop() shrinks the list, the loop continues iterating toward the original length, eventually trying to access an index that has been shifted out of bounds. The code below shows a safer approach.

numbers = [1, 2, 3, 4, 5]
odds = [num for num in numbers if num % 2 != 0]
print(odds) # [1, 3, 5]

The safest approach is to build a new list instead of modifying the original during iteration. This solution uses a list comprehension to create a new list containing only the desired elements. This completely avoids the index shifting that causes an IndexError when you're removing items inside a loop.

  • It iterates over the original list without changing it, making the operation predictable and safe.
  • This is the recommended pattern whenever you need to filter a list based on some condition.

Real-world applications

Putting these prevention methods into practice helps you build robust applications that can handle messy, real-world data from CSVs and JSONs.

Handling partial data in CSV processing with try-except

When you're processing data from sources like CSV files where rows can be incomplete, a try-except IndexError block provides a reliable way to handle missing fields without crashing your script. Before applying these error handling techniques, you'll need to understand reading CSV files fundamentals.

def process_csv_row(row):
try:
name = row[0]
age = int(row[1])
score = float(row[2])
return f"{name} (age {age}) scored {score}"
except IndexError:
return "Incomplete data row"

sample_data = [
["Alice", "29", "95.5"],
["Bob", "32"],
["Charlie", "41", "88.0"]
]

for row in sample_data:
print(process_csv_row(row))

The process_csv_row function is built to safely handle lists that might not have the expected number of items. It uses a try block to optimistically access three elements from each list it receives.

  • If a list is too short, like the one for "Bob," trying to access an index that doesn't exist triggers an IndexError.
  • The except block catches this specific error, preventing a crash and returning a fallback string instead.

This pattern ensures the program can continue running through all the sample_data, even when some of the data is inconsistent.

Safely navigating nested JSON data with get()

When working with nested data like JSON, chaining the dictionary’s get() method lets you safely access information without causing an error if a key doesn't exist.

def extract_user_info(user_data):
name = user_data.get('name', 'Unknown')
# Safe navigation of nested structures
email = user_data.get('contact', {}).get('email', 'No email')
skills = user_data.get('skills', [])[:2] # Get up to 2 skills safely
return f"User: {name}, Email: {email}, Top skills: {skills}"

users = [
{'name': 'Alice', 'contact': {'email': 'alice@example.com'}, 'skills': ['Python', 'SQL', 'JavaScript']},
{'name': 'Bob', 'skills': ['Java']},
{'contact': {'phone': '555-1234'}}
]

for user in users:
print(extract_user_info(user))

The extract_user_info function shows a robust way to handle dictionaries that might be missing keys. Instead of direct access that could crash, it relies on the get() method to provide a fallback value if a key isn't found.

  • Notice how it navigates nested data. By providing an empty dictionary {} as a default for contact, the code can safely attempt to get an email from it.
  • It also safely handles lists. Using get() with an empty list [] as a default for skills and then slicing with [:2] ensures you get up to two items without risking an error.

Get started with Replit

Turn your knowledge into a real tool with Replit Agent. Describe what you want to build, like “a script that processes a CSV with missing columns” or “an image rotator that cycles through a list of URLs.”

Replit Agent handles the coding, tests for errors, and deploys your app for you. It’s a faster way to get from concept to a working application. Start building with Replit.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.