Slicing is how Python lets you grab a chunk of a list without writing a loop. Pulling the first five reviews on a product page, paginating a search result, taking the top three items by price, reversing a wishlist: all of it uses the same compact [start:stop:step] syntax. This lesson covers what slices return, how the bounds and step interact, the rules around negative indices, and how slice assignment lets you replace, grow, shrink, or delete chunks of a list in one shot.
A slice has the shape cart[start:stop]. Python returns a new list containing the elements from index start up to, but not including, index stop.
cart[0:2] gives you the items at index 0 and 1. Index 2 is the stop value, and it's exclusive, so HDMI is not in the result. This is the same half-open convention that range() uses: a range or slice with stop produces exactly stop - start items, which makes the math easy to reason about.
The clearest way to picture this is to imagine the indices as positions between the elements, not on top of them. A list of five items has six slot positions: 0 before the first item, 5 after the last.
cart[1:4] cuts at slot position 1 and slot position 4, taking everything between those two cuts: Cable, HDMI, Webcam. The number of items is always stop - start, which here is 4 - 1 = 3.
Cost: A slice allocates a new list and copies stop - start references into it. For a five-item slice, the work is invisible. For a million-item slice, you're paying O(k) in both time and memory, where k is the slice length. The list contents are not deep-copied; only the references are.
You can leave out start, stop, or both. Python fills in sensible defaults: start defaults to 0, and stop defaults to the length of the list. That gives you four readable patterns for everyday slicing.
cart[:3] reads as "everything up to index 3". cart[2:] reads as "from index 2 to the end". cart[:] reads as "from start to end", which gives you a full copy of the list.
A real e-commerce example: paginating a product list. Suppose each page shows ten products and the customer is on page three.
The math is clean: page n starts at (n - 1) * page_size and ends at n * page_size. Because the stop is exclusive, no overlap or gap between pages.
Slicing has one trait that surprises people coming from other habits: it never raises IndexError, even when the bounds are wildly outside the list.
Three different "wrong" slices, three quiet answers. cart[0:100] clamps the stop to the actual list length and returns whatever's there. cart[5:10] starts past the end, so there's nothing to take, and you get an empty list. cart[2:1] has a stop before the start, which is also empty.
Compare that to indexing, which is strict:
The lenient behavior of slices is intentional. It lets you write code like "give me the first ten products" without checking how many actually exist. If the catalog has only three, you get those three back instead of a crash.
No length check needed. The slice silently does the right thing.
Slices accept negative indices the same way indexing does: -1 is the last element, -2 the second-to-last, and so on. This is useful when you want "the last N items" without computing the length yourself.
prices[-3:] reads as "from three positions before the end to the end". The omitted stop defaults to the list length, so you get the last three items.
Negative values work for the stop too. cart[:-1] is "everything except the last item":
You can mix positive and negative bounds freely. cart[1:-1] skips the first and last items:
A small reference for the common shapes:
| Slice | Meaning |
|---|---|
cart[:n] | First n items |
cart[-n:] | Last n items |
cart[n:] | Everything from index n onward |
cart[:-n] | Everything except the last n items |
cart[1:-1] | Everything except the first and last items |
cart[:] | Full copy of the list |
A slice can take a third value: a step, written as cart[start:stop:step]. The step controls how far the slice jumps between values. The default step is 1, which is what every example so far has used implicitly.
products[::2] starts at index 0, runs to the end, and takes every second element. You'd reach for this when you need to sample a list, alternate between groups, or pick every Nth row.
Combining a step with explicit bounds works as you'd expect:
Start at 1, jump by 2, stop before 8. So the slice produces 1, 3, 5, 7. The next value would be 9, which is past the stop, so the slice ends.
Cost: A larger step doesn't make slicing cheaper. The slice still walks the positions internally; it just skips most of them. huge_list[::1000] is no faster than huge_list[::1] per element scanned, though it allocates a much smaller result list.
A step of 0 is not allowed. Python raises ValueError because a step of zero would mean "don't move", which can't produce a finite sequence.
That's the one slicing operation that raises. Every other out-of-range or backwards combination quietly returns an empty list.
The step can be negative, which makes the slice walk the list backwards. When the step is negative, the default start becomes the last index and the default stop becomes "before the first index". So cart[::-1] walks from end to start.
cart[::-1] is the idiomatic Python way to get a reversed copy of a list. It's short, fast, and every Python developer recognizes it on sight. The original list is untouched; you get a new list back.
If you only want every second element in reverse, combine the step with the direction:
Start at the last item (Keyboard), step by -2 (two positions backwards each time), stop before the beginning. So you visit indices 5, 3, 1.
With a negative step, the bounds also flip in meaning: start is the higher position and stop is the lower one. If you write cart[0:4:-1], you get an empty list, because going forward from 0 to 4 with a negative step makes no sense.
The first call returns empty because the direction doesn't match the bounds. The second call walks from index 4 down to (but not including) index 0, which gives the last three items.
Every slice produces a fresh list. The new list holds references to the same objects the original list held, but the list container itself is independent. Mutating the slice does not change the original.
first_two is a separate list. Appending to it doesn't touch cart. This is different from plain assignment (first_two = cart), which would have made both names point to the same list.
The cart[:] form is the most direct way to copy a list. It's used wherever you want to walk a list while modifying the original safely, or hand a list to a function without letting that function mutate yours.
snapshot keeps the original three items because it was a copy of the list at that moment. The slice did the copying work.
There's a wrinkle to be aware of: cart[:] is a shallow copy. The new list is independent, but if the items inside are themselves mutable (other lists, dicts, custom objects), both lists share those inner objects. Modifying an inner object through one list shows up in the other.
Cost: cart[:] allocates a new list and copies all references. For a list of N items, that's O(N) in time and memory. It's the right tool for "I need a separate list", but don't reach for it when you just want to read.
Slicing can also appear on the left side of an assignment, which lets you replace a chunk of a list in one expression. The right-hand side has to be an iterable, and Python overwrites the targeted slice with the iterable's contents.
The simplest case is replacing the same number of items.
The slice cart[1:3] covers two items (Cable and HDMI), and the right-hand side has two items, so it's a clean swap. Nothing about the surrounding list changes.
The replacement doesn't have to be the same size. You can shove more items into a slice than the slice originally had, and the list grows:
The two-item slice gets replaced by three items, so the list now has five elements. Or you can replace with fewer items, and the list shrinks:
Three items vanish and one takes their place.
The most extreme version of "shrink" is replacing with an empty list, which deletes the slice outright:
The same effect is more directly expressed with del:
del cart[a:b] and cart[a:b] = [] do the same work. Prefer del when your intent is to remove; it reads better and signals the operation clearly.
Slice assignment can also insert without removing anything, by targeting an empty slice. cart[2:2] is an empty slice at position 2, and assigning to it splices new items in:
That's how you insert a batch of items at a specific position without using insert() in a loop.
Cost: Slice assignment that changes the list's length forces the items after the slice to shift in memory. For a list of N items where you replace at index i, that's roughly O(N - i) work to do the shift. Appending at the very end (cart[len(cart):] = [...]) avoids the shift and is cheap.
A real e-commerce scenario: replacing a contiguous range of products in a catalog with new ones.
Three items leave, four items take their place, and the surrounding list is preserved. One assignment, no loop.
A slice looks like an assignable thing on any sequence, so people try to mutate strings or tuples through it. Both fail, and the error is worth recognizing.
What's wrong with this code?
Strings are immutable. You can read a slice from a string (order_id[4:] gives "1023"), but you can't assign to it. Slice assignment only works on mutable sequences, which in everyday Python means lists, bytearray, and a few others. Tuples have the same restriction:
The fix for both cases is to build a new value with the change you want. For a string, slice the parts you want to keep and concatenate:
Fix:
The other trap is using a step of 0, which we covered earlier. The error there is ValueError, not TypeError, so it's distinguishable from the immutable-sequence problem at a glance.
A small composition exercise. Take a product list with prices, sort it, then slice off the top three. This combines slicing with sorted() to show how slicing fits into common workflows.
sorted() returns a new list sorted by the second element of each tuple (the price), in descending order. The slice by_price[:3] then keeps the first three. Two operations, no loops, one readable expression for "top 3 by price". This is the kind of place slicing earns its keep.
10 quizzes