Python’s negative indices (lst[-1] = last item) are convenient but can still raise IndexError if the negative index is more negative than the list length. [1, 2][-5] raises just like [1, 2][5] does. Here is how negative indexing actually works and 4 safe patterns.

How negative indexing works under the hood
lst = ['a', 'b', 'c']
# Python converts negative index to positive: lst[-i] becomes lst[len(lst) - i]
# So lst[-1] = lst[3-1] = lst[2] = 'c'
# And lst[-5] = lst[3-5] = lst[-2] which is also negative, so it tries lst[3+(-2)] = lst[1]
# Wait, no: negative becomes positive ONCE. -5 becomes 3 - 5 = -2, still negative, raises IndexError
print(lst[-5]) # IndexError: list index out of range
Minimal reproducer
def last_n(lst, n):
return lst[-n] # IndexError if n > len(lst)
last_n([1, 2, 3], 5) # IndexError: list index out of range
Fix 1: Bounds check on negative
def safe_nth_from_end(lst, n):
if n > len(lst):
return None # or raise a clearer error
return lst[-n]
Fix 2: Use slicing (never raises)
lst = [1, 2, 3]
last_one = lst[-1:] # [3] - safe even if empty
last_five = lst[-5:] # [1, 2, 3] - safe, returns what is available
Fix 3: max(0, …) to clamp
def take_last(lst, n):
return lst[max(0, len(lst) - n):]
# take_last([1,2,3], 5) returns [1,2,3]
# take_last([1,2,3,4,5], 2) returns [4, 5]
Fix 4: itertools.islice for streams
from collections import deque
# For an iterator that does not support len(), keep a sliding window
last_3 = deque(maxlen=3)
for item in some_stream():
last_3.append(item)
# last_3 now always has the most recent 3 items, never raises
Negative index rules of thumb
| Expression | Result |
|---|---|
| [1,2,3][-1] | 3 (last) |
| [1,2,3][-3] | 1 (first, valid) |
| [1,2,3][-4] | IndexError (too negative) |
| [][-1] | IndexError (empty list) |
| [1,2,3][-1:] | [3] (slicing, safe) |
| [][-1:] | [] (slicing, safe) |
Frequently Asked Questions
Why does Python support negative indices at all?
For convenient access to the end of a sequence without computing length first. lst[-1] is more readable than lst[len(lst)-1]. It also enables clean slicing like lst[-3:] for “last 3 items.” The behavior is borrowed from R and several functional languages.
Why does negative slicing never raise but negative indexing does?
Slicing is “give me whatever is in this range, clamp if needed” (returns empty if nothing). Indexing is “give me exactly this element, fail if missing.” The slice/index distinction is consistent across all sequence types in Python (list, tuple, str, bytes, range, NumPy array).
Is there a way to get a default value instead of IndexError on negative index?
No built-in equivalent of dict.get() for lists. Common patterns: (lst[-n:] or [None])[0] for the nth-from-end with None default, or lst[-n] if n <= len(lst) else default. Or wrap in your own safe_get function.
Do dicts use negative indices?
No. dict keys are arbitrary hashable objects, not ordered positions. d[-1] looks for a key with value -1. To access the “last inserted” key (Python 3.7+ preserves insertion order), use list(d.keys())[-1] or next(reversed(d)).
Are negative indices slower than positive ones?
In CPython, both have identical O(1) cost for lists. The negative-to-positive conversion is a single subtraction. No measurable performance difference. Use whichever reads more clearly.
