Python raises IndexError: list index out of range when you index a sequence (usually a list) at a position that does not exist. Valid indexes for a list of length n are 0 through n-1 (and -1 through -n). The quickest fixes are a len check before you index, a narrow try/except IndexError, safer loops with enumerate, and never hard-coding a length you already changed with pop, remove or split.
The Python docs list IndexError as the exception raised when a sequence subscript is out of range. That is the whole story. The hard part is working out why the index is wrong: the list was empty, you used len as an index (off-by-one), a split returned fewer fields than you expected, or you shrank the list while still walking the old length.
I reproduced every example below on Python 3.13.5, and I re-ran the empty-list traceback on 3.11.17 and 3.14.8. All three printed the same message and caret underlining. Paths in the tracebacks point at my test scripts under /workspace/seo-posts-nov07/test/python/.
In this guide:
- What IndexError looks like
- The 10 causes at a glance
- 1. Indexing an empty list
- 2. Off-by-one: using len(lst) as an index
- 3. Length changed after pop or delete
- 4. split() or CSV rows shorter than you expected
- 5. Negative indexes past -len(lst)
- 6. Mutating a list while walking a fixed length
- 7. Nested lists: the crash is often the inner index
- 8. Strings raise a closely related IndexError
- 9. range() that starts at 1 (or ends past len)
- 10. Slicing is forgiving; indexing is not
- Reliable fixes checklist
- Frequently asked questions
What IndexError looks like
Here is the smallest crash. The list is empty, so there is no index 0:
nums = []
print(nums[0])
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c01_empty.py", line 2, in <module>
print(nums[0])
~~~~^^^
IndexError: list index out of range
Since Python 3.11, the traceback underlines the lookup. The tildes sit under nums and the carets under [0], so you know which expression failed. The same traceback printed on 3.11.17 and 3.14.8.
If the variable was None instead of a list, you would see a different exception: TypeError: 'NoneType' object is not subscriptable. That family of crashes is covered in TypeError: NoneType is not subscriptable.
The 10 causes at a glance
| # | Cause | How to spot it | Fix |
|---|
| 1 | Empty list | IndexError on [0] | Check if lst: / len |
| 2 | Off-by-one (lst[len(lst)]) | Index equals length | Use 0..len-1 or [-1] |
| 3 | Length changed after pop | Worked before a delete | Re-check length; use deque |
| 4 | Short split / CSV row | Fewer parts than expected | Pad or guard len(parts) |
| 5 | Negative index beyond -len | lst[-n-1] | Bound-check negatives too |
| 6 | Mutating while indexing a fixed range | Crash mid-loop after pop | Iterate a copy / reverse |
| 7 | Unpacking short rows by index | Crash on row[2] | Pad fields; validate rows |
| 8 | String index out of range | Message says string | Same rules; check len |
| 9 | Nested list, wrong level | Carets under inner [] | Print each level; check lengths |
| 10 | range / slice vs index mix-up | Slice works, [i] crashes | Remember slices are forgiving |
1. Indexing an empty list
An empty list has length 0. There is no valid positive or negative index. nums[0] and nums[-1] both raise.
Fix: test emptiness first, or catch narrowly when absence is exceptional:
items = ["a", "b"]
idx = 5
if 0 <= idx < len(items):
print(items[idx])
else:
print("out of range: idx", idx, "len", len(items))
try:
print(items[5])
except IndexError as e:
print("caught:", repr(e))
it = iter(items)
print("first:", next(it, None))
print("second:", next(it, None))
print("third:", next(it, None))
out of range: idx 5 len 2
caught: IndexError('list index out of range')
first: a
second: b
third: None
next(iterator, default) is a clean way to pull the first item without indexing. For optional "first element" reads, prefer that or an explicit if items: guard over a bare items[0].
2. Off-by-one: using len(lst) as an index
A list of three items has indexes 0, 1 and 2. Index 3 is one past the end - the classic off-by-one:
items = ["a", "b", "c"] # len 3, valid indexes 0..2
print("len:", len(items))
print(items[3])
len: 3
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c02_off_by_one.py", line 3, in <module>
print(items[3])
~~~~~^^^
IndexError: list index out of range
Fix: the last item is items[-1] or items[len(items) - 1], never items[len(items)]. When you loop with indexes, use range(len(items)) (which stops before len) or, better, enumerate. Sequence indexing rules live in the Python built-in types docs.
3. Length changed after pop or delete
You computed an index when the list was longer. After pop, that index is gone:
queue = ["one", "two", "three"]
print("before pop:", queue, "len", len(queue))
queue.pop()
print("after pop:", queue, "len", len(queue))
print(queue[2])
before pop: ['one', 'two', 'three'] len 3
after pop: ['one', 'two'] len 2
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c03_after_pop.py", line 5, in <module>
print(queue[2])
~~~~~^^^
IndexError: list index out of range
Fix: re-check len after every mutation, or use a structure that matches the access pattern. For queues, collections.deque gives cheap left pops without leaving stale indexes around:
from collections import deque
q = deque(["one", "two", "three"])
print("popleft:", q.popleft())
print("remaining:", list(q))
popleft: one
remaining: ['two', 'three']
Lists, tuples, sets and dicts each behave differently under indexing and membership; if you are choosing a structure, see list vs tuple vs set vs dict.
4. split() or CSV rows shorter than you expected
str.split returns only the parts that exist. A line with two fields is length 2, so index 2 raises:
line = "Alice,admin"
parts = line.split(",")
print("parts:", parts, "len", len(parts))
print(parts[2]) # expected email at index 2
parts: ['Alice', 'admin'] len 2
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c04_split_short.py", line 4, in <module>
print(parts[2]) # expected email at index 2
~~~~~^^^
IndexError: list index out of range
The same pattern shows up when you pull fields by index from a short CSV row:
row = "id,name".split(",") # only 2 fields; expected 3
print("row:", row)
uid, name, email = row[0], row[1], row[2]
row: ['id', 'name']
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c07_csv_row.py", line 4, in <module>
uid, name, email = row[0], row[1], row[2]
~~~^^^
IndexError: list index out of range
Fix: pad missing fields, or refuse the row early:
line = "Alice,admin"
parts = line.split(",")
if len(parts) < 3:
parts = parts + [""] * (3 - len(parts))
name, role, email = parts[0], parts[1], parts[2]
print("name:", name, "role:", role, "email:", repr(email))
first, *rest = "a,b".split(",")
print("first:", first, "rest:", rest)
name: Alice role: admin email: ''
first: a rest: ['b']
5. Negative indexes past -len(lst)
Negative indexes count from the end. -1 is the last item, -len(lst) is the first. One step further raises:
letters = ["x", "y", "z"]
print("letters[-1]:", letters[-1])
print("letters[-3]:", letters[-3])
print(letters[-4])
letters[-1]: z
letters[-3]: x
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c05_negative.py", line 4, in <module>
print(letters[-4])
~~~~~~~^^^^
IndexError: list index out of range
Fix: bound-check negatives the same way you check positives:
letters = ["x", "y", "z"]
n = -4
if -len(letters) <= n < len(letters):
print(letters[n])
else:
print("negative index out of range:", n)
def at(seq, i, default=None):
try:
return seq[i]
except IndexError:
return default
print("at(letters, 99):", at(letters, 99))
print("at(letters, -1):", at(letters, -1))
negative index out of range: -4
at(letters, 99): None
at(letters, -1): z
A tiny at() helper keeps call sites clean when a missing position should become a default instead of a crash. For missing keys on dicts you want a different exception and fix path - see KeyError in Python.
6. Mutating a list while walking a fixed length
Hard-coding the old length (or caching len once) and then pop-ing inside the loop is a reliable way to walk off the end:
nums = [10, 20, 30, 40]
i = 0
while i < 4: # hard-coded old length
print("i", i, "value", nums[i])
if nums[i] % 20 == 0:
nums.pop(i)
else:
i += 1
i 0 value 10
i 1 value 20
i 1 value 30
i 2 value 40
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c06_mutate_while_iter.py", line 5, in <module>
print("i", i, "value", nums[i])
~~~~^^^
IndexError: list index out of range
After popping 20, the list is shorter, but the loop still aims for index 3. Fix: iterate indexes in reverse when deleting, or build a new list:
nums = [10, 20, 30, 40]
for i in range(len(nums) - 1, -1, -1):
if nums[i] % 20 == 0:
nums.pop(i)
print("after reverse pop:", nums)
items = ["red", "green", "blue"]
for i, value in enumerate(items):
print(i, value)
after reverse pop: [10, 30]
0 red
1 green
2 blue
7. Nested lists: the crash is often the inner index
Chained indexes fail at the first bad level. The caret underline tells you which [] broke:
matrix = [[1, 2], [3, 4]]
print("matrix[0]:", matrix[0])
print(matrix[1][2])
matrix[0]: [1, 2]
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c09_nested.py", line 4, in <module>
print(matrix[1][2])
~~~~~~~~~^^^
IndexError: list index out of range
matrix[1] exists ([3, 4]), but that inner list only has indexes 0 and 1. Fix: print each level, check len(matrix[r]) before matrix[r][c], and treat ragged rows as data you must validate. When you are pasting sample matrices while debugging, the helpers on mfaysal.com/free-tools are handy for a quick look.
8. Strings raise a closely related IndexError
Strings are sequences too. Index past the end and you get a sibling message:
word = "hi"
print("len:", len(word))
print(word[2])
len: 2
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c08_str_index.py", line 4, in <module>
print(word[2])
~~~~^^^
IndexError: string index out of range
Same rules: valid indexes are 0..len-1. Slicing a string is forgiving (word[10:20] returns ''); indexing is not. Prefer slicing when you want a tolerant read, and indexing when a missing character should be an error.
9. range() that starts at 1 (or ends past len)
People sometimes write range(1, len(names) + 1) because they want "human" numbering, then use i as an index:
names = ["Ada", "Lin"]
for i in range(1, len(names) + 1):
print(i, names[i])
1 Lin
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c10_range_vs_len.py", line 4, in <module>
print(i, names[i])
~~~~~^^^
IndexError: list index out of range
Index 1 printed Lin; index 2 does not exist. Fix: keep display numbers separate from indexes:
names = ["Ada", "Lin"]
for i, name in enumerate(names, start=1):
print(i, name) # i is 1-based for display
Or loop with range(len(names)) and add 1 only when you print. Mixing 1-based counting with 0-based indexing is one of the most common IndexError sources in beginner code and in quick scripts.
10. Slicing is forgiving; indexing is not
Slices clamp to the ends of the sequence. A single index does not:
data = [1, 2, 3]
print("slice data[5:]:", data[5:])
print("slice data[1:10]:", data[1:10])
print(data[5])
slice data[5:]: []
slice data[1:10]: [2, 3]
Traceback (most recent call last):
File "/workspace/seo-posts-nov07/test/python/c11_slice_vs_index.py", line 5, in <module>
print(data[5])
~~~~^^^
IndexError: list index out of range
That difference surprises people who "tested" with a slice in the REPL and then shipped a bare index. Fix: know which one you mean. Use a slice when an empty result is fine. Use an index (and a guard) when a missing position is a real error. The data structures tutorial covers list operations in the official Python tutorial.
Reliable fixes checklist
- Read the caret line: which index expression failed?
- Print
len(seq) and the index you asked for. - Prefer
enumerate over hand-rolled range(len(...)) when you need both index and value. - After
pop/remove/split, re-check length before indexing. - Pad or reject short CSV / split rows instead of assuming field counts.
- Use reverse iteration (or a new list) when deleting by index.
- Remember: slices forgive, indexes do not.
- Catch
IndexError narrowly at boundaries; do not swallow Exception.
deque, used earlier for queue-style pops, lives in the collections module. Reach for it when you need cheap left-side operations; keep a plain list when random access by index is the main job.
Frequently asked questions
What does IndexError: list index out of range mean?
It means you used square brackets to read a position that does not exist on a list (or another sequence). For a list of length n, valid indexes are 0 through n-1 and -1 through -n.
Why does my loop raise IndexError after a few iterations?
Usually the list shrank (pop/remove) while the loop still used the old length, or you started range at 1 and used those numbers as indexes. Print len and the index each time the body runs.
How do I safely get the first or last item?
Check if the list is non-empty, then use lst[0] or lst[-1]. Alternatively use next(iter(lst), default) for the first item without indexing.
Why does a slice work but lst[i] crash?
Slices clamp to the sequence bounds and can return []. A single index must be in range or Python raises IndexError.
Is IndexError the same as string index out of range?
Same exception class, different message. Strings, lists, tuples and other sequences all raise IndexError when a subscript is out of range.
Should I catch IndexError or check len first?
If missing indexes are normal, check len or use a helper with a default. If they should never happen at that layer, let IndexError propagate or catch it narrowly at a boundary.
How is IndexError different from KeyError?
IndexError is for out-of-range sequence positions. KeyError is for missing keys on mappings (dicts). They look similar in a traceback but the fixes differ.
Does enumerate prevent IndexError?
It prevents the common off-by-one from hand-rolled ranges, because it only yields real indexes. It will not save you if you later index with a different, invalid number.
Last checked: 7 November 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.