List Slicing
Indexing gets you one element out of a list. Slicing gets you a range of them, and it comes up constantly in interviews: splitting a list in half, checking a string against its reverse, peeling off everything but the first or last element. The syntax is short, but a few of its rules are not what people expect the first time they see them.
Slice basics: start:stop:step
O(k)A slice is written a[start:stop:step]. It returns a new list containing elements from start up to but not including stop, moving step at a time. Leaving out a piece falls back to a sensible default: the beginning of the list, the end of the list, or a step of 1.
step works the same way in the third slot. This grabs every second element starting at index 1, then every third element from the whole list:
Note
stop is always exclusive. a[1:4] returns 3 elements, at indices 1, 2, and 3, never the element at index 4. This is the same rule range() follows, and it is worth internalizing once instead of re-deriving it every time you write a slice.
Negative indices and negative steps
O(n)A negative index counts from the end instead of the start. a[-1] is the last element, and a negative number in a slice works the same way, it just marks a position relative to the end rather than the start:
letters[::-1] is the classic one-liner for reversing a list: a step of -1 walks backward from the end to the start. Compare it to list.reverse(), which reverses in place instead of returning a new list:
Note
[::-1] copies, reverse() mutates. After reversed_copy = original[::-1], original is untouched, so it still prints in its starting order right next to the new reversed list. Only after calling original.reverse() does original itself change. Reach for [::-1] when you need to keep the original order around too, and reverse() when you don't.
Why out-of-range slices never raise
no errorA slice's start and stop get clamped to the list's actual bounds instead of erroring when they run past the end or before the beginning:
Indexing a single element has no range to clamp to, so that same kind of out-of-bounds request behaves completely differently:
Note
This is a common source of "silent" bugs. A typo'd slice bound just quietly returns a shorter list, or an empty one, instead of telling you something is wrong. If you expect a slice to have a specific length, check len() on the result rather than assuming the bounds you wrote were honored exactly.
Slices are shallow copies
watch outa[:] makes a new list object, so operations on the new list, like appending to it, never affect the original. But the elements themselves are not copied, only the references to them. If an element is a mutable object, like a nested list, both lists end up pointing at the exact same object:
Note
shallow.append("z") only changed shallow, since that added a new slot to the copy's own list. But shallow[0].append(3) mutated the inner list itself, and both original[0] and shallow[0] point at that same inner list, so the 3 shows up in both. If you need a copy where nested objects are independent too, use copy.deepcopy() instead of a slice.
Slice assignment
A slice can appear on the left side of = too. This replaces the elements in that range with whatever you assign, and the replacement doesn't need to be the same length as what it's replacing:
Assigning an empty list to a slice deletes that range entirely, and del on a slice does the same thing more directly:
Note
del numbers[::2] removes every other element, starting at index 0, in a single line, no loop needed. Because slice assignment can change the list's length, be careful about looping over indices while also assigning to a slice inside the same loop, the indices can shift out from under you mid-loop.
Where list slicing shows up in interviews
Slicing is most useful as a fast sanity check, not usually the "real" solution an interviewer wants:
- Valid Palindrome can be checked in one line with
s == s[::-1]. Most interviewers want the two-pointer version instead, since it runs in O(1) extra space and shows you understand the pattern, buts[::-1]is the fastest way to sanity-check your two-pointer answer against. - Reorder List involves reversing the second half of a linked list. If you ever rebuild that same idea on a plain Python list instead of a linked list, slicing the list in half and reversing one half with
[::-1]is the direct translation of that step.