Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. List index out of range in Python: causes and fixes
Tools

List index out of range in Python: causes and fixes

List index out of range means you asked for a position the list does not have. Six causes, including the one that prints most of your output first.

By Max Arthur
Co-Founder & Content Marketer·October 9, 2026·8 Min read
White text reading 'List index out of range in Python' on a dark purple dotted background

IndexError: list index out of range means you asked a list for a position it does not have. Python lists start at 0, so a list of three items has positions 0, 1 and 2. Position 3 does not exist, and asking for it raises rather than returning nothing.

nums = [10, 20, 30]
print(nums[3])
# IndexError: list index out of range

The single most useful consequence of zero-indexing: the last valid index is always len(list) - 1, so len() itself is never a valid index.

nums = [10, 20, 30]
print(nums[len(nums)])
# IndexError: list index out of range

Every output on this page came from Python 3.13 running under Pyodide 0.28.3.

The message names the type

Worth noticing, because it tells you what you are actually holding:

s = 'abc'
print(s[5])
# IndexError: string index out of range

t = (1, 2)
print(t[9])
# IndexError: tuple index out of range

If you expected a list and the message says string index out of range, your variable is a string. That is often the real bug, and the index is just where it surfaced.

Six causes

1. Off by one in range(len(...)). The classic, and it has a distinctive symptom covered in the next section.

for i in range(len(nums) + 1):
    print(nums[i])

2. The list is empty. An empty list has no valid index at all, not even 0.

rows = []
print(rows[0])
# IndexError: list index out of range

3. A filter matched nothing. This is the empty-list case wearing a disguise, and it passes every test where at least one item matches.

nums = [1, 3, 5]
evens = [n for n in nums if n % 2 == 0]
print(evens[0])
# IndexError: list index out of range

The list comprehension is correct. There simply are no even numbers in [1, 3, 5], so evens is [].

4. Removing items while looping over the list. The list shrinks underneath the loop, and the indexes computed at the start stop being valid.

nums = [1, 2, 3, 4]
for i in range(len(nums)):
    if nums[i] % 2 == 0:
        nums.remove(nums[i])
# IndexError: list index out of range

range(len(nums)) was fixed at 0, 1, 2, 3 when the loop began. By the time i reaches 3, two items have been removed and the list only has positions 0 and 1. Build a new list instead:

nums = [n for n in nums if n % 2 != 0]

5. Data that is shorter than you assumed. Splitting a line and reading parts[2] works until one row has fewer fields. Files with a blank final line cause this constantly.

6. A negative index on an empty list. -1 normally means "last item", which is a safe way to reach the end. It is not safe when there is no end:

nums = [10, 20, 30]
print(nums[-1])   # 30

rows = []
print(rows[-1])
# IndexError: list index out of range

The one that prints first, then fails

The off-by-one case behaves in a way that misleads people, and it is worth seeing the actual output:

nums = [10, 20, 30]
for i in range(len(nums) + 1):
    print(nums[i])
10
20
30
IndexError: list index out of range

Every value printed correctly. The loop did all of its real work, and only then crashed on an iteration that should never have run.

This matters because it changes what the error is telling you. Output that looks complete followed by an IndexError usually means the loop ran one step too many, not that your data or your logic is wrong. Nothing before the error is suspect.

range(len(nums)) produces 0, 1, 2 and is correct. Adding + 1 makes it 0, 1, 2, 3, and 3 is one past the end.

Print the length before you guess

When the cause is not obvious, two values settle it immediately:

print(len(nums), i)

The list's length and the index you used. If the index is greater than or equal to the length, you have found the bug without reading any more code.

Put that line immediately before the failing access rather than at the top of the function. Lists change as a program runs, and the length at the top may not be the length at the moment of failure, which is exactly the situation in the remove-while-looping case above.

For data that arrived from a file or an API, print the whole thing once:

print(repr(rows))

repr() rather than print(rows), because it shows quotes and escapes. That distinguishes an empty list from a list holding one empty string, and it makes a trailing blank line in a parsed file visible instead of invisible. Those two cases produce identical-looking output otherwise and have different fixes.

The fixes, in the order to prefer them

Loop over the list, not over indexes. If you do not need the position, do not create one:

for n in nums:
    print(n)

This cannot go out of range. There is no index to get wrong.

Use enumerate() when you need the position too.

for i, n in enumerate(nums):
    print(i, n)
# 0 10
# 1 20
# 2 30

enumerate counts as it goes, so the index always matches a real item. It is the direct replacement for range(len(...)) and the reason that pattern is rarely needed. There is more on it in how to loop with an index.

Check before you reach in, when the index comes from outside your code:

if len(rows) > 0:
    print(rows[0])

Catch it when an empty result is a normal outcome rather than a bug:

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

Use this one sparingly. A try around a genuine off-by-one hides the mistake instead of fixing it.

Nested lists hide which index failed

With a grid, the message is no help at all. These two mistakes are different, and Python reports them identically:

grid = [[1, 2], [3, 4]]

print(grid[5][0])   # IndexError: list index out of range
print(grid[0][5])   # IndexError: list index out of range

The first asked for row 5 of a two-row grid. The second asked for column 5 of a two-column row. Same message, different bug, and no indication of which index was the problem.

Split the access to find out:

row = grid[5]       # fails here if the row is missing
value = row[0]      # fails here if the column is missing

Whichever line raises tells you which index is wrong. This is the fastest way through any chained subscript, and it applies equally to data parsed from JSON where data["items"][3]["name"] can fail at several points.

Ragged data makes this worse, because the rows are not all the same length:

for row in grid:
    print(len(row))

Printing the lengths first is often quicker than reasoning about where the data came from.

The same mistake on other types

Reaching for something that is not there raises a different exception depending on what you reached into. Recognising which one you got tells you what kind of container you are holding:

Code Exception
nums[3] on a 3-item list IndexError: list index out of range
rows.pop() on [] IndexError: pop from empty list
user['age'] on a dict without it KeyError: 'age'
nums.index(9) when 9 is absent ValueError: 9 is not in list

pop from empty list is worth noticing because it names the operation rather than the index, which makes it easier to find than the generic message.

KeyError is the dictionary equivalent, and it has a clean fix that lists do not have:

user = {'name': 'Ada'}
print(user.get('age'))      # None
print(user.get('age', 0))   # 0

.get() returns None, or a default you choose, instead of raising. There is no list method that does this, which is why the list version needs a length check, a slice, or a try.

ValueError from .index() catches people who expect it to return -1 the way some other languages do. It does not. If you need to know whether an item is present, test with in first:

if 9 in nums:
    print(nums.index(9))

Slices never raise

This is the most useful thing on this page, and it is missing from most explanations:

nums = [10, 20, 30]
print(nums[1:99])   # [20, 30]
print(nums[99:])    # []

Neither of those is an error. A slice that runs past the end is silently trimmed to whatever exists, and a slice starting past the end gives an empty list.

So nums[0] on an empty list raises, and nums[0:1] on the same list returns []. When you want "the first item if there is one", a slice expresses it without a length check:

first = nums[:1]    # [] or [10]

The flip side is that slices will not tell you when your assumptions are wrong. An index is loud and a slice is quiet, so pick the one that matches what an empty result means in your program. If missing data is a bug, let it raise.

Want to build the intuition on real lists? Start with the Python track and get the off-by-one errors somewhere they cost nothing.

Keep reading

More from the blog

White text reading 'Why React lists need unique keys' on a dark purple dotted background
October 16, 2026·7 min readTools

Each child in a list should have a unique key prop

The unique key prop warning in React still renders your list. Here is the bug it predicts, shown with real output from two runs.

Read more
White text reading 'Too many re-renders in React' on a dark purple dotted background
October 15, 2026·9 min readTools

Too many re-renders in React: the one real cause

Too many re-renders always means a state update running during render. Here is how to spot which of the four shapes you have written.

Read more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentReact DeveloperPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs Mimo
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs Sololearn
Devpuff vs freeCodeCamp
Read All Comparisons