How to slice a list in Python
Use slice syntax: items[start:stop] returns a new list from start up to but not including stop. So on ["a", "b", "c", "d", "e"], items[1:4] gives ['b', 'c', 'd'] — three items, not four.
A slice always returns a new list — the original is untouched — and it never raises IndexError, even when the bounds run off the end. The syntax is items[start:stop:step], and every part is optional. The same rules apply to strings and tuples ("CompileBytes"[7:] is 'Bytes'), but lists get one extra power the immutable types don't: you can assign to a slice. Each example below runs on this page — hit Run, then edit and run it again.
1items[start:stop]Recommended
The one rule that trips everyone up: a slice is half-open. It includes start and excludes stop. The upside is that the length of a slice is simply stop - start, and items[:n] plus items[n:] always reassemble the original with no overlap and no gap.
Output
Prints ['b', 'c', 'd'], 3, ['a', 'b'], then []. A slice whose start is at or past its stop isn't an error — it's just an empty list.
2Omitting ends and using negative indices
Leave start off and it defaults to the beginning; leave stop off and it defaults to the end. Negative numbers count backwards from the end, which is how you write "the last three" (items[-3:]) and "everything but the last" (items[:-1]) without touching len().
Output
The last two lines are the contrast worth remembering: items[2:99] quietly clamps and returns ['c', 'd', 'e'], while the plain index items[99] raises IndexError: list index out of range. Slices are forgiving; indexing is not. That's a feature when you want "up to 10 results" from a list that might only hold three — but it also means a typo'd bound fails silently.
3The step argument
The third slot is the step: items[start:stop:step]. A step of 2 takes every other item; a negative step walks the list backwards, which makes items[::-1] the standard one-liner for a reversed copy.
Output
Output: [0, 2, 4, 6, 8], [1, 3, 5, 7, 9], [2, 5], [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], [9, 7, 5, 3, 1]. Note that [::-1] returns a new reversed list — use items.reverse() if you want to reverse in place instead.
4Slice assignment (and items[:] as a copy)
items[:] read as a value is a shallow copy — a new list holding the same objects. Assigned to, a slice edits the list in place: the replacement doesn't have to be the same length, so one statement can replace, insert, or (with del) delete a run of items.
Output
The copy ends up ['z', 'b', 'c', 'd', 'e'] while the original stays ['a', ...]. Shallow is the catch: with rows = [[1, 2], [3, 4]], rows[:] gives a new outer list but the inner lists are the same objects, so mutating one shows up in both. Use copy.deepcopy() when that matters. Also note items[:] = [...] is not the same as items = [...]: the first mutates the list every other reference can see, the second just rebinds your name.
5Common slice idioms at a glance
Every row below is real output for items = ["a", "b", "c", "d", "e"]:
| Slice | Returns | What it does |
|---|---|---|
| items[1:4] | ['b', 'c', 'd'] | Index 1 up to — not including — 4 |
| items[:3] | ['a', 'b', 'c'] | First three; start defaults to 0 |
| items[2:] | ['c', 'd', 'e'] | Index 2 through the end |
| items[-3:] | ['c', 'd', 'e'] | Last three, no len() needed |
| items[:-1] | ['a', 'b', 'c', 'd'] | Everything but the last item |
| items[:] | ['a', 'b', 'c', 'd', 'e'] | Shallow copy of the whole list |
| items[::2] | ['a', 'c', 'e'] | Every other item |
| items[::-1] | ['e', 'd', 'c', 'b', 'a'] | Reversed copy |
| items[2:99] | ['c', 'd', 'e'] | Out-of-range bounds clamp — never IndexError |
| items[4:1] | [] | Start at or past stop, so nothing matches |
6Common variation: islice and slice objects
Slice syntax needs __getitem__, so it doesn't work on generators — squares()[1:5] raises TypeError: 'generator' object is not subscriptable. itertools.islice takes the same start/stop/step arguments and consumes the iterator lazily, so it works on infinite sources. And when the same slice shows up in several places, slice() lets you name it once and reuse it.
Output
Prints [1, 4, 9, 16], [0, 9, 36, 81], ['c', 'd', 'e'], then -3 None None. Two limits worth knowing: islice rejects negative indices (it can't see the end of a stream), and it consumes what it skips, so the source iterator has advanced by the time it's done.
Frequently asked questions
Why does items[1:3] return only two items?
Because slices are half-open: the start index is included and the stop index is excluded. That keeps the length equal to stop - start and makes items[:n] and items[n:] split a list cleanly with no overlap and no gap.
Does slicing a list copy it?
Yes — every slice returns a new list, and items[:] is the idiomatic shallow copy (same thing as items.copy()). It is shallow: nested lists or dicts inside are shared with the original, so mutating one is visible from both. Use copy.deepcopy() for an independent nested copy.
Why does an out-of-range slice not raise IndexError?
Slice bounds are clamped to the list, so items[2:99] on a five-item list just returns everything from index 2 onward, and items[99:] returns []. Only plain indexing raises — items[99] gives IndexError: list index out of range.
How do I slice a generator or other iterator?
Use itertools.islice(iterable, start, stop, step) — generators are not subscriptable, so gen[1:5] raises a TypeError. Note that islice does not accept negative indices and consumes the items it skips.