Lists and List Operations
The three types of sequences mentioned above - lists, tuples and strings, also have a slicing operation which allows us to retrieve a slice of the sequence i.e. a part of the sequence.
Try it: Indexing and Slicing
How Python indexes a sequence from 0 (or from the end with negative numbers) and how a slice start:stop:step picks positions — with the exact CPython rules for omitted and out-of-range bounds.
How it works
- Positions run 0 … len−1; negative positions count back from the end (−1 is the last).
- An index outside the string raises IndexError.
- A slice resolves start and stop (omitted → the ends; out of range → clamped), then walks by step, stopping before stop.
- A negative step walks backwards; a step of 0 is a ValueError.
Default run (7 steps): 'PYTHONIC'[1:6:2]: a slice copies characters from start up to (not including) stop, moving by step. … 'PYTHONIC'[1:6:2] → 'YHN' (positions 1, 3, 5).
Simplified: Works on a short string (up to 16 printable ASCII characters); lists slice by exactly the same rules.
Loading the simulation…
What Slicing Does
When you work with lists, tuples, or strings, you often need to extract a portion of the sequence rather than the whole thing. Slicing is the operation that lets you do this. Instead of accessing a single element by its index, slicing lets you specify a range of elements and get them all at once as a new sequence. The key insight is that slicing always returns a new sequence—it does not modify the original, and it does not create a reference to it.
Slicing uses the syntax sequence[start:stop:step]. The start index is where the slice begins (inclusive), the stop index is where it ends (exclusive), and the step determines how many positions to jump between selected elements. If you omit any of these parameters, Python uses sensible defaults: start defaults to 0, stop defaults to the length of the sequence, and step defaults to 1.
Understanding Slice Boundaries
The most important rule to remember about slicing is that the start index is inclusive but the stop index is exclusive. This means if you write list[1:4], you get elements at indices 1, 2, and 3—but not 4. This boundary rule is consistent across all sequence types in Python.
In the diagram above, the list contains five fruits at indices 0 through 4. When you slice with [1:4], the start boundary (1) is inclusive, so 'banana' at index 1 is included. The stop boundary (4) is exclusive, so 'elderberry' at index 4 is not included. The result is a new list containing only 'banana', 'cherry', and 'date'.
Negative Indices in Slicing
Python allows you to use negative indices in slices, just as you can with single-element indexing. A negative index counts backward from the end of the sequence: -1 refers to the last element, -2 to the second-to-last, and so on. When you use negative indices in a slice, they follow the same inclusive-start, exclusive-stop rule as positive indices.
In the diagram, you can see that each element has two valid index labels. For a five-element list, index -3 is the same as index 2, index -2 is the same as index 3, and so on. When you write list[-3:-1], you are asking for elements starting at index -3 (inclusive) and ending at index -1 (exclusive). That gives you 'cherry' and 'date', the same result as list[2:4].
The Step Parameter
The third parameter in a slice is the step value. It controls how many positions to skip between selected elements. A step of 1 (the default) selects every element in the range. A step of 2 selects every other element, a step of 3 selects every third element, and so on. You can also use a negative step to traverse the sequence backward.
The diagram traces through list[1:5:2] on a five-element list. Starting at index 1, Python selects 'banana'. Then it jumps forward by the step value (2 positions), landing at index 3, and selects 'date'. The next jump would land at index 5, which is the stop boundary, so the slice ends. The result is a new list containing ['banana', 'date'].
Slicing Creates a New Sequence
A critical fact about slicing is that it always returns a new sequence. When you slice a list, you get a brand-new list object. This is different from simple assignment: if you write new_list = old_list, both variables refer to the same list object in memory, and changes to one will affect the other. But if you write new_list = old_list[:], you get a completely independent copy.
To make a safe copy of a list or other sequence, use the slicing operation. A simple assignment statement does not create a copy—both variables will refer to the same object. This is especially important when working with complex objects or when you need to modify one sequence without affecting another.
This behavior applies to all sequence types. Slicing a tuple returns a new tuple, slicing a string returns a new string, and slicing a list returns a new list. The original sequence is never modified, and the new sequence is completely independent.
Slicing Across Sequence Types
One of the elegant features of Python is that slicing works the same way on lists, tuples, and strings. The syntax is identical, the boundary rules are identical, and the step parameter works the same way. The only difference is the type of object you get back: slice a list and you get a list, slice a tuple and you get a tuple, slice a string and you get a string.
| Sequence Type | Slice Syntax | Result Type | Original Modified? |
|---|---|---|---|
| List | my_list[1:4] | List | No |
| Tuple | my_tuple[1:4] | Tuple | No |
| String | my_string[1:4] | String | No |
Slicing behavior is consistent across all sequence types. The syntax is the same, the boundaries work the same way, and the original is never modified.
Worked Example: Extracting a Slice
Slicing a List of Numbers
You have a list of numbers [10, 20, 30, 40, 50, 60, 70]. Extract every other element starting from index 1, up to (but not including) index 6.
- 1Identify the parameters
You need to start at index 1, stop before index 6, and use a step of 2 to get every other element. The slice notation is [1:6:2].
- 2Trace through the selection
Starting at index 1, you select 20. Jump by 2 (step), landing at index 3, select 40. Jump by 2 again, landing at index 5, select 60. Jump by 2 again would land at index 7, which is beyond the stop boundary of 6, so the slice ends.
- 3Verify the result
The slice [1:6:2] returns a new list [20, 40, 60]. The original list is unchanged.
[20, 40, 60]
Common Mistakes with Slicing
Common mistake Forgetting that the stop index is exclusive
What it looks likeWriting list[0:3] expecting to get four elements (indices 0, 1, 2, 3), when it actually returns only three (indices 0, 1, 2).
Why it failsThe stop index marks the boundary where the slice ends, and that boundary is not included. This is by design to make slicing consistent and predictable.
Do this insteadRemember: start is inclusive, stop is exclusive. To include index 3, write list[0:4].
Common mistake Assuming a slice modifies the original sequence
What it looks likeWriting new_list = old_list[1:4] and then modifying new_list, expecting old_list to change as well.
Why it failsSlicing always creates a new, independent sequence. Changes to the slice do not affect the original.
Do this insteadIf you need both variables to refer to the same list, use assignment without slicing: new_list = old_list. If you need an independent copy, use slicing as intended.
Common mistake Using assignment instead of slicing to copy a list
What it looks likeWriting copy_list = original_list to create a copy, then modifying copy_list and being surprised that original_list changed too.
Why it failsAssignment makes both variables refer to the same object in memory. To create an independent copy, you must use slicing.
Do this insteadUse copy_list = original_list[:] or copy_list = original_list[0:len(original_list)] to create a true copy.Common mistake Misunderstanding negative indices in slices
What it looks likeWriting list[-1:-4] expecting to get the last three elements, when it actually returns an empty list.
Why it failsNegative indices count backward, so -1 is the last element and -4 is the fourth-to-last. Writing [-1:-4] means start at the last element and stop before the fourth-to-last, which is moving backward in the sequence without a negative step.
Do this insteadTo get the last three elements, write list[-3:] or list[-3:len(list)]. To use negative indices in reverse order, use a negative step: list[-1:-4:-1].
Best Practices for Safe List Copying
When you need to create a copy of a list or other sequence, always use slicing. The most straightforward way is to use the full-range slice [:], which selects the entire sequence and returns a new copy. This works for lists, tuples, and strings, and it makes your intent clear to anyone reading your code.
The slicing approach is simple, readable, and works consistently across all sequence types. It is the Pythonic way to copy sequences and is widely used in real Python code.
Practice: Predict the Slice Result
What do you think happens?
Given the list numbers = [0, 10, 20, 30, 40, 50, 60, 70], what does numbers[2:6:2] return?
Reveal answer
[20, 40]
The slice [2:6:2] starts at index 2 (value 20), stops before index 6 (value 50), and uses a step of 2. Starting at index 2 (20), jump by 2 to index 4 (40), jump by 2 again to index 6 (60), but 6 is the stop boundary so it is not included. The result is [20, 40].
Practice: Copy a List Safely
You have a list original = [1, 2, 3, 4, 5]. Write a slice expression that creates an independent copy of this list. Then explain why a simple assignment like copy = original would not work for this purpose.
Hints
- Use the full-range slice syntax to copy the entire list.
- Remember that assignment creates a reference, not a copy.
- Think about what would happen if you modified the copy—would the original change?
Summary
- 1Slicing uses the syntax sequence[start:stop:step] to extract a portion of a list, tuple, or string. The start index is inclusive, the stop index is exclusive, and the step controls how many positions to skip between selected elements.
- 2Negative indices count backward from the end of the sequence and work the same way in slices as they do in single-element indexing. The boundary rules (inclusive start, exclusive stop) still apply.
- 3Slicing always returns a new, independent sequence. The original is never modified, and changes to the slice do not affect the original.
- 4To safely copy a list or other sequence, use slicing (e.g., copy = original[:]). A simple assignment (copy = original) creates a second reference to the same object, not a copy.
- 5Slicing works identically on lists, tuples, and strings. The syntax and boundary rules are the same; only the type of the returned object differs.
At a glance
- Slicing extracts a portion of a sequence using [start:stop:step], where start is inclusive, stop is exclusive, and step controls the interval between selected elements.
- Negative indices count backward from the end and follow the same boundary rules as positive indices in slices.
- Slicing always creates a new, independent sequence; the original is never modified.
- To safely copy a list or sequence, use slicing (e.g., copy = original[:]); simple assignment creates a reference, not a copy.
- Slicing syntax and behavior are consistent across lists, tuples, and strings.