10.3 List Indexing and Slicing
Positive and negative indexing, chaining indices into nested lists, IndexError, and slicing with l[start:stop:step] -- including reversal, copying, and slice assignment.
Indexing
Positive indices count from the left; negative indices count from the right.
Positive indices count from the left, starting at 0. Negative indices count from the right, starting at -1 — reaching the end without needing len(l) - 1.
>>> l = [10, 20, 30, 40, 50]
>>> l[0], l[2]
(10, 30)
>>> l[-1]
50
Nested Indexing
Chain indices to reach into nested lists — the first index selects the inner list, the second selects an element within it.
>>> nested = [[1, 2], [3, 4]]
>>> nested[0][1]
2
IndexError
Accessing an index outside the list’s actual range raises IndexError immediately — unlike slicing below, which never does.
>>> l[10]
Traceback (most recent call last):
IndexError: list index out of range
Slicing
l[start:stop:step] extracts a sub-list — the exact same syntax as string slicing (see 9.4 String Indexing and Slicing), including the same “never raises” behavior for out-of-range bounds.
>>> l = [10, 20, 30, 40, 50]
>>> l[1:3] # start:stop
[20, 30]
>>> l[:2] # start defaults to 0
[10, 20]
>>> l[2:] # stop defaults to the end
[30, 40, 50]
>>> l[::2] # every 2nd element
[10, 30, 50]
Reverse
A negative step of -1 reverses the list, mirroring the string idiom exactly.
>>> l[::-1]
[50, 40, 30, 20, 10]
Copy
l[:] creates a new list object — unlike the equivalent string slice, which CPython can safely reuse because strings are immutable (see 9.4 String Indexing and Slicing). Lists cannot be shared this way, so a genuine copy is always made.
>>> copy_l = l[:]
>>> copy_l is l # a DIFFERENT object, not the same one
False
l[:] is a shallow copy — fine for a flat list, but nested lists inside are still shared. See 10.11 Copying and Mutability for the full shallow-vs-deep comparison.
Slicing as an Assignment Target
Unlike a string slice, a list slice can be assigned to — replacing a whole range of elements at once, covered in 10.4 Modifying Lists.
Quick Interview Answer
“Indexing and slicing on a list use the exact same
l[start:stop:step]syntax as strings — the difference is what they hand back and what they allow. Direct indexing out of range raisesIndexError; slicing never does, it just clamps.l[::-1]reverses, same as strings. The one list-specific difference:l[:]genuinely copies the list, because lists are mutable and can’t be safely shared the way an immutable string slice can — and unlike a string, a list slice can also be used as an assignment target to replace a whole range in place.”
Common Mistakes
- Assuming
l[:]and the equivalent string slice behave identically under the hood — for strings CPython can return the same object; for lists it must always allocate a new one, since the result could otherwise be mutated unexpectedly. - Forgetting
l[:]is only a shallow copy — a nested list inside is still the same shared object in both the copy and the original. - Expecting an out-of-range slice to raise, the way direct indexing does —
l[100:200]on a short list just returns[], no error.
Add More Questions to This Guide
Know a question that should be here? Share it and help the community!
Open Google Form