How does Python list slicing work, including step and negative indices?
Slicing uses the syntax `seq[start:stop:step]` and returns a new list containing elements from index `start` up to but not including `stop`, stepping by `step`. Negative indices count from the end; a negative step reverses direction. Omitted parts default to the beginning, end, or step of 1.
How to think about it
How to approach this in an interview
Slicing looks simple but the edge cases — negative indices, negative steps, out-of-range bounds — trip up a lot of candidates. The cleanest way to explain it is to start with the three parameters and build up: start, then stop, then step. The mental model that helps most: think of indices as sitting between elements, not on them.
Basic syntax
The full form is seq[start:stop:step]. Stop is exclusive — the element at the stop index is not included. That matches Python’s range(start, stop) convention.
data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
data[2:5] # [2, 3, 4] — indices 2, 3, 4 (stop=5 excluded)
data[:4] # [0, 1, 2, 3] — from start
data[6:] # [6, 7, 8, 9] — to end
data[:] # full shallow copy
data[::2] # [0, 2, 4, 6, 8] — every other element
data[::-1] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] — reversed
Negative indices and steps
Negative index -k is equivalent to len(data) - k. So -1 is the last element, -2 the second-to-last, and so on.
data[-3:] # [7, 8, 9] — last three elements
data[-3:-1] # [7, 8] — stop is still exclusive
data[8:2:-1] # [8, 7, 6, 5, 4, 3] — step backwards from index 8 down to 3
Interactive playground — experiment with slicing
Out-of-range slices do not raise
data[100:] # [] — no IndexError; slices clamp silently
data[:100] # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Contrast this with single-index access: data[100] raises IndexError. Slices are more forgiving because they describe a range rather than a specific position.
Slice assignment modifies in place
nums = [1, 2, 3, 4, 5]
nums[1:3] = [20, 30, 40] # replace two elements with three
print(nums) # [1, 20, 30, 40, 4, 5]
nums[::2] = [0, 0, 0] # when step != 1, replacement must match length
Strings and tuples follow the same rules
Both return a new object of the same type. String slicing is a clean way to parse fixed-width fields without regex: row[10:20] for a fixed-width log format.