Python List Slicing

Imagine having a long loaf of bread. Sometimes, you want the whole thing, but other times you only need a few slices. List slicing in Python works in a similar way—it lets you extract specific portions of a list rather than working with the entire list at once. This is incredibly useful when dealing with large datasets, manipulating specific ranges of data, or simply extracting the information you need.

To perform slicing, you use square brackets [] along with a special syntax. This syntax involves specifying the starting index (where your slice begins), the stopping index (where it ends), and the step size (how many elements you skip between each included element). With this simple technique, you can access elements from the beginning, middle, or end of a list, modify, remove, or insert elements in a list, and even create copies of entire lists.

Throughout this tutorial, we’ll learn how this syntax works and explore examples of different slicing techniques. Let’s get started!

Syntax

The basic syntax for slicing a list is:

python list slicing syntax
  • start: The index at which the slice begins (inclusive). If omitted, the slice starts from the beginning of the list.
  • stop: The index at which the slice ends (exclusive). If omitted, the slice goes up to the end of the list.
  • step: The interval between elements in the slice. If omitted, the default step is 1.

So, if you have a list named L, writing L[start:stop:step] gives you a new list containing the elements from L starting at the start index, going up to (but not including) the stop index, and taking elements in steps of step.

Basic Slicing

Let’s look at a simple example of list slicing. Imagine you have a list of letters named L:

L = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

Now, let’s say you want to extract a portion of this list, specifically the elements from ‘c’ to ‘g’. You can do this using slicing:

print(L[2:7])
# Output: ['c', 'd', 'e', 'f', 'g']

In this example, we start at index 2, which corresponds to the letter ‘c’. Then we slice up to, but not including, index 7. This means we stop just before the letter ‘h’ at index 7. The result is a new list that contains the elements at indices 2 through 6, giving us the desired sequence of letters from ‘c’ to ‘g’.

python list slicing illustration

Slicing with Negative Indices

When slicing lists in Python, you can use negative indices as well. This allows you to reference elements from the end of the list. For example, the index -1 represents the last element, -2 represents the second-to-last element, and so on.

Consider the same list of letters. If you wanted to extract the elements from ‘c’ to ‘g’ using negative indices, you could write:

L = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
print(L[-7:-2])
# Output: ['c', 'd', 'e', 'f', 'g']
python list slicing negative indices

Slicing with Positive and Negative Indices

Python allows you to mix positive and negative indices within the same slice. This can be particularly useful when you want to select elements relative to both the beginning and the end of your list.

L = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
print(L[2:-5])
# Output: ['c', 'd']

Slicing with a Step

In addition to specifying start and stop indices, slicing also allows you to introduce a step value, enabling you to extract elements at regular intervals.

Let’s take our familiar list of letters as an example. If you want to get every other element starting from index 2 (the letter ‘c’) and going up to, but not including, index 7, you can use a step value of 2:

# Extract every 2nd item between position 2 to 7
L = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
print(L[2:7:2])
# Output: ['c', 'e', 'g']
python list slicing specifying step size

Negative Step

A negative step reverses the order of elements. In the example below, the slice starts at index 6 and ends before index 1, taking every second element in reverse order.

# Return every 2nd item between position 6 to 1
L = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
print(L[6:1:-2])
# Output: ['g', 'e', 'c']

When using a negative step, ensure the start index is greater than the stop index.

Slice at the Beginning and to the End

When you omit the start index, the slice begins at the start of the list. So, L[:stop] is equivalent to L[0:stop].

For example, to get the first three items of a list named L, you would use L[:3].

# Slice the first three items from the list
L = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
print(L[:3])
# Output: ['a', 'b', 'c']

On the other hand, when you omit the stop index, the slice goes up to the end of the list. So, L[start:] is equivalent to L[start:len(L)].

For example, to obtain the last three items, you’d use L[6:].

# Slice the last three items from the list
L = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
print(L[6:])
# Output: ['g', 'h', 'i']

Reversing a List

Omitting both the start and stop indices while specifying a negative step value of -1 reverses the order of elements in the slice.

L = ['a', 'b', 'c', 'd', 'e']
print(L[::-1])
# Output: ['e', 'd', 'c', 'b', 'a']

Replacing a Slice

Slicing can also be used to modify lists. You can replace elements within a list by assigning a new list to a slice of the original list.

For example, if you want to replace the elements from index 1 to 3 (inclusive) with the values 1, 2, and 3, you can do:

# Modify multiple list items
L = ['a', 'b', 'c', 'd', 'e']
L[1:4] = [1, 2, 3]
print(L)
# Output: ['a', 1, 2, 3, 'e']

Interestingly, you can replace a single element with multiple elements using this technique.

# Replace multiple elements in place of a single element
L = ['a', 'b', 'c', 'd', 'e']
L[1:2] = [1, 2, 3]
print(L)
# Output: ['a', 1, 2, 3, 'c', 'd', 'e']

Inserting Elements

You can efficiently insert new elements into a list without replacing existing ones by specifying a zero-length slice.

To insert elements at the beginning of a list, you can use a slice starting from the beginning (L[:0]) and assign the new elements to it.

# Insert at the start
L = ['a', 'b', 'c']
L[:0] = [1, 2, 3]
print(L)
# Output: [1, 2, 3, 'a', 'b', 'c']

If you want to insert elements at the end, you can use a slice that starts at the current length of the list (L[len(L):]) and assign the new elements there.

# Insert at the end
L = ['a', 'b', 'c']
L[len(L):] = [1, 2, 3]
print(L)
# Output: ['a', 'b', 'c', 1, 2, 3]

Inserting in the middle of the list is also possible. By using a slice with the same start and stop index, you can insert elements at the desired position within the list without overwriting any existing elements.

# Insert in the middle
L = ['a', 'b', 'c']
L[1:1] = [1, 2, 3]
print(L)
# Output: ['a', 1, 2, 3, 'b', 'c']

Removing a Slice

You can remove multiple elements from the middle of a list using slicing techniques. One way to do this is by assigning an empty list to the desired slice.

# Remove elements from index 1 to 5 (exclusive)
L = ['a', 'b', 'c', 'd', 'e']
L[1:5] = []
print(L)
# Output: ['a']

Alternatively, you can use the del statement to achieve the same result.

# Remove elements from index 1 to 5 (exclusive)
L = ['a', 'b', 'c', 'd', 'e']
del L[1:5]
print(L)
# Output: ['a']

Copying the Entire List

In Python, when you assign one list to another (e.g., new_list = old_list), you’re not creating a true copy. Instead, you’re creating a new reference that points to the same underlying list object. Any changes made to either new_list or old_list will affect both, as they share the same data.

To create an actual copy of the list, you can use the slicing operator. By omitting both the start and stop indices (L1[:]), you create a copy of the entire list L1. This means L2 is a new list object containing the same elements as L1, but changes to one list won’t affect the other.

L1 = ['a', 'b', 'c', 'd', 'e']

# Create a shallow copy of L1
L2 = L1[:]
print(L2)
# Output: ['a', 'b', 'c', 'd', 'e']

print(L2 is L1)
# Output: False (Confirms they are different list objects)

It’s important to note that this slicing technique creates a shallow copy. This means a new list is made, but if the original list contains mutable objects (like nested lists or dictionaries), those objects are not duplicated. Both lists share references to the same mutable objects.

For deeper copies where nested mutable objects are also duplicated, you can use the list.copy() method or the copy.deepcopy() function (if you need to copy nested objects recursively).

Important Considerations

In list slicing, certain considerations are important to avoid unexpected behavior.

Out of Range Indices

When you use indices that go beyond the boundaries of a list, Python adjusts the indices to the nearest valid values instead of causing an error. For example:

# Attempt to slice up to index 100 (which doesn't exist)
L = ['a', 'b', 'c', 'd', 'e', 'f']
print(L[3:100])
# Output: ['d', 'e', 'f']

Here, L[3:100] starts at index 3 and attempts to go up to index 100. Since the list ends before index 100, the slice includes all elements from index 3 to the end.

Empty Slice

If you try to create a slice where the starting index is the same as or greater than the stopping index (and the step is positive), you’ll get an empty list:

# Attempt to slice starting at index 5 and ending at index 3
L = ['a', 'b', 'c', 'd', 'e', 'f']
print(L[5:3])
# Output: []

Step Size of 0

Specifying a step size of 0 is not allowed and will result in a ValueError:

L = ['a', 'b', 'c', 'd', 'e', 'f']
print(L[1:4:0])
# ValueError: slice step cannot be zero