Why Slicing With Index Out Of Range Works In Python?

Python slicing means to access a subsequence of a sequence type using the notation [start:end]. A little-known feature of slicing is that it has robust end indices. Slicing is robust even if the end index is greater than the maximal sequence index. The slice just takes all elements up to the maximal element. If the start index is out of bounds as well, it returns the empty slice.

Slicing: Stop Index Out of Bounds & Start Index Within Bounds

What happens if the start index of a given slicing operation is within the bounds while the stop index is outside?

In this case, slicing will consider all elements up to the maximal possible index. As it cannot slice further over non-existing elements, it stops and gracefully returns whatever slice it has already accessed.

Here’s an example for a string:

>>> s = 'hello'
>>> s[1:100]
'ello'
>>> s[3:100]
'lo'

Here’s an example for a list:

>>> lst = [1, 2, 3]
>>> lst[1:100]
[2, 3]
>>> lst[3:100]
[]

The final example lst[3:100] is explained next!

Slicing: Start and Stop Index Out of Bounds

The slicing operation doesn’t raise an error if both your start and stop indices are larger than the sequence length. This is in contrast to simple indexing—if you index an element that is out of bounds, Python will throw an index out of bounds error. However, with slicing it simply returns an empty sequence.

Here’s an example for a string:

>>> s = 'hello'
>>> s[100:200]
''

And here’s an example for a list:

>>> lst = [1, 2, 3]
>>> lst[100:200]
[]

This is what happens if you try to index an element outside the bounds:

>>> lst = [1, 2, 3]
>>> lst[100]
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    lst[100]
IndexError: list index out of range

Why Doesn’t Slicing Out of Bounds Result in an Error? [Design Motivation]

You may find the design decision of Python’s creators odd that they throw an error if you index out of bounds but they don’t if you slice out of bounds.

There’s no exact science here but I find the decision very sensible because of the following reasons.

Indexing is supposed to always return one single element. Slicing is supposed to return a subsequence of a variable number of elements. If you index a non-existent element, there’s nothing to return and Python must throw an error—everything else wouldn’t make any sense. But if you slice an out-of-bounds sequence, it makes perfect sense to return an empty sequence.

Yes, you could return None in the indexing case. However, this would make it impossible to differentiate between the two cases where a None element is stored in a list and where a list doesn’t have an element at all.

The documentation shows the ultimate reason for this:

“The slice of s from i to j with step k is defined as the sequence of items with index x = i + n*k such that 0 <= n < (j-i)/k. In other words, the indices are i, i+k, i+2*k, i+3*k and so on, stopping when j is reached (but never including j). When k is positive, i and j are reduced to len(s) if they are greater

This clearly explains why the semantics is the way it is: in our case, the indices i and j are reduced to len(s). So, you have a slice from len(s) to len(s) excluded which is the empty sequence by definition.

Slicing Puzzle: Test Your Skills

Can you solve the following puzzle about Python overshooting indices?

word = "galaxy"
print(word[4:50])

What is the output of this code snippet?

You can check your solution against our gold standard on the Finxter.com app and track your skill.

Slicing Puzzle: Test Your Skills Overshooting

Official Documentation

If you want to place in the docs that point to the explanation of this issue, here it is:

Degenerate slice indices are handled gracefully: an index that is too large is replaced by the string size, an upper bound smaller than the lower bound returns an empty string.” — Official Python 3 Documentation

>>> word[1:100]
'elpA'
>>> word[10:]
''
>>> word[2:1]
''

Related Video

Leave a Comment