IndexError in Python: where it comes from and how to fix it

IndexError reports an access to a position that does not exist in a list, a tuple or a string: the sequence is shorter than the code assumed.
5 min read
Believemy logo

Definition

A sequence has a length, and the code reading it makes an assumption about that length. As long as the two agree, nobody notices anything. The day the list is shorter than expected, Python has to choose between inventing a value and refusing. It refuses, and that refusal has a name: IndexError.

The error therefore happens as soon as an item is requested at a position that does not exist. Positions start at zero, which shifts everything by one compared with ordinary counting: a list of three items accepts 0, 1 and 2, and refuses 3, which would point at a fourth item.

PYTHON
colours = ["red", "green", "blue"]

# Three items, so three valid positions: 0, 1 and 2.
colours[2]  # "blue", the last one in the list

# Position 3 would point at a fourth item, which does not exist.
colours[3]

# IndexError: list index out of range

The message is literal: the requested position falls outside the existing range, which runs from zero to len(items) - 1. Nothing to decode, then, and the investigation comes down to one question: why is the sequence shorter than expected?

The rule applies to everything walked through by position: lists, tuples, strings. Dictionaries raise a KeyError instead, since they work by key rather than by position. Knowing which one shows up already says what kind of object the failing line works on.

Good to know

An IndexError can show up without a single position being written: items.pop() on an empty list raises the same error, with the message pop from empty list. The method removes the last item, and an empty list has none.


The three classic situations

Almost every IndexError comes from three scenarios, and recognising them saves time: each one is fixed somewhere else in the program.

The first is the off-by-one, the famous boundary mistake. It comes from the habit of counting the items, then writing the position as if counting started at one. Walking with range(len(items) + 1) or writing items[len(items)] overshoots by exactly one. This is the strongest argument for the direct for loop, which cannot overshoot since it counts nothing: the sequence unrolls itself.

The second is the empty sequence. results[0] looks harmless, and it is as long as one item remains: position zero only exists in a non-empty sequence. On a search that found nothing, that same line raises an IndexError. This scenario sails through the tests, then breaks in production the day the filter keeps nothing.

The third comes from manual splitting. A split on a malformed line hands back fewer pieces than expected, and reading the third field fails on the one line of the file that breaks the format. The program handles a thousand of them then stops dead, which looks like a random bug. The traceback points at the line of code, never at the line of the file: print the split value just before the access.


The three ways to avoid it

Three ways of writing make the error impossible rather than catching it afterwards. The table compares them, and the third deserves a word of explanation.

WritingWhat it brings
Walk with forNo position to handle, so no overshoot possible
Check the length firstif items: is enough to rule out the empty case
Slice rather than accessitems[:3] raises nothing, even on a shorter list

Slicing really is forgiving where direct access is strict. items[10:20] on a three-item list hands back an empty list, and items[:3] hands back whatever it finds, even on a single item. That is what makes slicing handy for reading the first three without knowing the total.

Warning

That forgiveness is also a trap. A wrong position inside a slice raises nothing: it produces an empty list, which the rest of the program treats as a normal result. The inconsistency surfaces much further away, in a shape that no longer looks like its cause.


What it tells you about your data

This error has a rare quality: it points at exactly the problem. A TypeError can come from ten different places, whereas a position out of range always means the same thing. The right question is therefore not how to avoid the error, but why this list is shorter than expected.

Nine times out of ten the answer lies upstream: a filter that ruled everything out, a file whose last line is blank, a query with no result. The quickest check is to print the length just before the access. If it reads zero, the real problem sits where the sequence was built.

Wrapping the access in a try without answering that question only moves the symptom: the program carries on with missing data, which resurfaces later. Catching the error keeps its meaning when the empty case is expected.


Frequently asked questions

Question

What does a negative position mean?

It counts from the end: items[-1] is the last item and items[-2] the one before, which saves recomputing the length. The overshoot rule does not change: on three items the valid negative positions stop at -3, and items[-4] raises an IndexError.

Question

How can an item be read without risking the error?

By slicing, which hands back an empty list rather than raising, or by a try followed by an except that plans for the empty case. Sequences have no equivalent of the dictionary get, for want of a sensible default value.

Question

Why does my code work locally but not in production?

Because real data holds a case missing from the test set: a blank line at the end of a file, a missing field, a search with no result. The code has not changed, the shape of the data has. Hence the value of a test on the empty collection.

Related terms

Discover our python glossary

Browse the terms and definitions most commonly used in development with Python.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.