PyPython example

How to slice a list in Python

5 min read▶ Runs in an isolated hosted runtimeUpdated Jul 2026

Quick answer

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.

slice.py

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().

ends.py

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.

step.py

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.

assign.py

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"]:

SliceReturnsWhat 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.

islice.py

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.