PythonSlicing

Slicing

Slicing lets you pull out a sub-range of a sequence — a string, list, or tuple — using a compact [start:stop:step] syntax. It is one of the most-used pieces of Python syntax, and because it works the same way across every built-in sequence type, learning it once pays off everywhere.

The start:stop:step Syntax

A slice is written as sequence[start:stop:step]. start is the index of the first item included, stop is the index before which slicing stops (the item at stop is never included — stop is exclusive), and step is how many positions to advance between items.

Python
letters = ["a", "b", "c", "d", "e", "f", "g"]
#            0    1    2    3    4    5    6

print(letters[1:4])     # ['b', 'c', 'd']  - indices 1, 2, 3 (4 is excluded)
print(letters[0:7:2])   # ['a', 'c', 'e', 'g']  - every 2nd item
print(letters[2:2])     # []  - start == stop, so nothing is included
Note
Because `stop` is exclusive, `letters[1:4]` gives you exactly `4 - 1 = 3` items. This "exclusive stop" rule is also why `letters[:n] + letters[n:]` always reconstructs the full sequence with no overlap and no gap.
Negative Indices

Negative numbers count from the end of the sequence: -1 is the last item, -2 the second-to-last, and so on. You can mix positive and negative indices freely within the same slice.

Python
letters = ["a", "b", "c", "d", "e", "f", "g"]

print(letters[-3:])     # ['e', 'f', 'g']  - last 3 items
print(letters[:-2])     # ['a', 'b', 'c', 'd', 'e']  - everything except the last 2
print(letters[-5:-2])   # ['c', 'd', 'e']
Omitting start, stop, or step

Any of the three parts can be left out, and each has a sensible default: start defaults to the beginning of the sequence, stop defaults to the end, and step defaults to 1.

Slice

Meaning

Example on [10, 20, 30, 40, 50]

seq[:]

Copy the whole sequence

[10, 20, 30, 40, 50]

seq[2:]

From index 2 to the end

[30, 40, 50]

seq[:3]

From the start up to (not including) index 3

[10, 20, 30]

seq[::2]

Every 2nd item, start to end

[10, 30, 50]

seq[1:4:2]

Index 1 up to 4, every 2nd item

[20, 40]

seq[::-1]

The whole sequence, reversed

[50, 40, 30, 20, 10]

Python
numbers = [10, 20, 30, 40, 50]

print(numbers[:])    # [10, 20, 30, 40, 50]  - a shallow copy of the whole list
print(numbers[2:])   # [30, 40, 50]
print(numbers[:3])   # [10, 20, 30]
print(numbers[::2])  # [10, 30, 50]
Reversing with [::-1]

Using a negative step of -1 with no start or stop walks the sequence from the end to the beginning, which makes [::-1] the idiomatic one-liner for reversing a string, list, or tuple without mutating the original.

Python
text = "Python"
print(text[::-1])   # nohtyP

digits = [1, 2, 3, 4, 5]
print(digits[::-1])  # [5, 4, 3, 2, 1]
print(digits)         # [1, 2, 3, 4, 5] - original list is untouched
Slicing Works the Same Across Sequence Types

Strings, lists, and tuples are all sequence types, and the exact same [start:stop:step] rules apply to each of them. The only difference is what kind of object the slice returns: slicing a string returns a string, slicing a list returns a list, and slicing a tuple returns a tuple.

Python
s = "abcdefg"
l = ["a", "b", "c", "d", "e", "f", "g"]
t = ("a", "b", "c", "d", "e", "f", "g")

print(s[1:4])   # 'bcd'          (str)
print(l[1:4])   # ['b', 'c', 'd'] (list)
print(t[1:4])   # ('b', 'c', 'd') (tuple)
Slice Assignment (Lists Only)

Because lists are mutable, you can assign to a slice to replace, grow, shrink, or delete a chunk of a list in place. Strings and tuples are immutable, so slice assignment only works on lists.

Python
numbers = [1, 2, 3, 4, 5]

# Replace a sub-range - the new value doesn't need to be the same length
numbers[1:4] = [20, 30]
print(numbers)  # [1, 20, 30, 5]

numbers = [1, 2, 3, 4, 5]
numbers[1:1] = [100, 200]   # inserts without removing anything
print(numbers)  # [1, 100, 200, 2, 3, 4, 5]

numbers = [1, 2, 3, 4, 5]
del numbers[1:3]            # deletes a chunk in place
print(numbers)  # [1, 4, 5]
Warning
Slice assignment replaces the *entire* selected range with whatever iterable you assign, even if the lengths differ. `numbers[1:4] = [20, 30]` is not an error even though 3 items are being replaced by 2 — the list simply shrinks to absorb the difference. This is convenient but can silently change the length of a list you expected to stay fixed-size, so double-check your slice bounds when mutating in place.
The Built-in slice() Object

Every start:stop:step you write inside square brackets is actually constructing a slice object behind the scenes. You can create one explicitly with the slice() built-in and reuse it across multiple sequences, which is handy when the same slicing pattern applies in several places in your code.

Python
middle = slice(1, 5, 2)

numbers = [10, 20, 30, 40, 50, 60]
letters = ["a", "b", "c", "d", "e", "f"]

print(numbers[middle])  # [20, 40]
print(letters[middle])  # ['b', 'd']
Tip
Naming a `slice` object is a nice way to give a meaningful label to a slicing pattern you use repeatedly, instead of repeating the same `[1:5:2]` in multiple places and hoping they stay in sync.