Concepts / Lists and List Operations

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.

  • Programming

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.

inclusiveexclusive0'apple'1'banana'start=12'cherry'3'date'4'elderberry'stop=4 (not included)
When you write list[1:4], which elements are actually included? This diagram shows how the start and stop indices map to the actual positions in the list.

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.

'apple'index 0 or -5'banana'index 1 or -4'cherry'index 2 or -3'date'index 3 or -2'elderberry'index 4 or -1
How do negative indices map to positions in a list? This shows both positive and negative index labels for the same elements.

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.

beginnextnextnextboundary reachedStart at index 1Select 'banana'(index 1)Jump by step=2Select 'date' (index3)Jump by step=2Index 5 reached (stopboundary)
When you use list[1:5:2], which elements are actually selected, and in what order?

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 TypeSlice SyntaxResult TypeOriginal Modified?
Listmy_list[1:4]ListNo
Tuplemy_tuple[1:4]TupleNo
Stringmy_string[1:4]StringNo

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.

Identify 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].

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

Verify 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

  • Forgetting that the stop index is exclusive

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

    Fix: Remember: start is inclusive, stop is exclusive. To include index 3, write list[0:4].

  • Assuming a slice modifies the original sequence

    Slicing always creates a new, independent sequence. Changes to the slice do not affect the original.

    Fix: If 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.

  • Using assignment instead of slicing to copy a list

    Assignment makes both variables refer to the same object in memory. To create an independent copy, you must use slicing.

    Fix: Use copy_list = original_list[:] or copy_list = original_list[0:len(original_list)] to create a true copy.

  • Misunderstanding negative indices in slices

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

    Fix: To 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?

  • [20, 40]
  • [20, 30, 40, 50]
  • [20, 40, 60]
  • [30, 50]
Reveal answer

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

EASY

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

  1. Slicing 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.
  2. Negative 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.
  3. Slicing always returns a new, independent sequence. The original is never modified, and changes to the slice do not affect the original.
  4. To 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.
  5. Slicing works identically on lists, tuples, and strings. The syntax and boundary rules are the same; only the type of the returned object differs.

Key Takeaways

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