What exceeding a list's bounds means
A Python list with n items has valid positive indices from 0 to n - 1. Python also accepts negative indices, which count from the end: valid ones run from -n to -1. Any index outside those two ranges is out of bounds, and reading it raises an error.
my_list = [1, 2, 3]
print(my_list[0]) # 1
print(my_list[2]) # 3
print(my_list[-1]) # 3
print(my_list[-3]) # 1
print(my_list[3]) # IndexError: list index out of range
print(my_list[-4]) # IndexError: list index out of range
The error is an IndexError with the message list index out of range. The message is the same whether the index was too large or too negative, so it does not tell you the index or the length; you have to print them.
Why it happens
The most common cause is forgetting that counting starts at 0. A list of 4 items has indices 0, 1, 2 and 3, so its length, 4, is not a valid index. The same off-by-one error appears in loops that run one step too far, such as for i in range(len(items) + 1).
An empty list is a second source. It has no valid index at all, so [][0] and [][-1] both raise IndexError. This surprises people who write items[-1] to get "the last item" without checking that the list has any.
The exercise: print the last element
The lesson shows a program that should print the last element of ages, which is 21, but crashes:
ages = [14, 32, 50, 21]
print(ages[len(ages)])
Here len(ages) is 4, and ages[4] does not exist. The last item is always at index len(ages) - 1, which is 3 here. The fix is one subtraction:
ages = [14, 32, 50, 21]
print(ages[len(ages) - 1])
This prints 21. In Python there is an even shorter spelling, ages[-1], which also prints 21 because negative indices count from the end. Both are correct. len(ages) - 1 is the form that works in most languages, which is why the lesson uses it.
Other places the same error appears
The error is not limited to reading from lists. Each of these raises an IndexError as well, with a slightly different message:
- Strings:
"abc"[3]givesstring index out of range. - Tuples:
(1, 2)[2]givestuple index out of range. - Assigning to a position:
a = [1, 2, 3]followed bya[3] = 9giveslist assignment index out of range. Assignment never makes a list longer; useappendfor that. pop:[1, 2, 3].pop(5)givespop index out of range, and[].pop()givespop from empty list.
NumPy arrays
If you use NumPy, the message names the index and the size. For a = np.array([1, 2, 3]), reading a[3] gives IndexError: index 3 is out of bounds for axis 0 with size 3. In a two-dimensional array the axis number says which dimension is too small: np.zeros((2, 2))[0, 2] reports axis 1 with size 2. The rule is the same as for lists: valid indices are -size to size - 1.
What does not raise an error
Slices are forgiving. A slice that goes past the end is trimmed instead of failing:
my_list = [1, 2, 3]
print(my_list[1:10]) # [2, 3]
print(my_list[5:]) # []
Also, list.insert with a large position does not fail: a.insert(10, 4) on [1, 2, 3] simply appends and gives [1, 2, 3, 4]. These calls can hide a wrong index, so they do not replace checking your indices.
How to avoid it
- Loop over the items directly (
for item in items) when you do not need the position, or usefor i, item in enumerate(items)when you do. Then there is no index to get wrong. - When you do need indices, use
range(len(items)), whose last value islen(items) - 1. - Before reading
items[-1]oritems[0], check that the list is not empty:if items:. - When you catch the error in the wild, print
len(items)and the index just before the failing line, and compare them.
Key takeaways
- Valid indices for a list of length
nare0ton - 1and-nto-1. - The last item is
items[len(items) - 1]oritems[-1]. items[len(items)]is always out of bounds.- Reading or assigning an index outside a list raises
IndexError, while slicing never does.
