Guide Python Beginner

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.

3 min read

Indexing

flowchart LR A["10\n0 / -5"] --- B["20\n1 / -4"] --- C["30\n2 / -3"] --- D["40\n3 / -2"] --- E["50\n4 / -1"]

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 raises IndexError; 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