Indexing, Slicing, and Sequence Rules
Key Takeaways
- Python sequence indexes start at 0, so the last valid positive index is len(sequence) - 1.
- Negative indexes count from the right, with -1 selecting the last element and -2 selecting the element before it.
- A slice uses start, stop, and optional step, and the stop index is excluded every time.
- Indexing outside a sequence raises IndexError, but slicing can safely use boundaries beyond the sequence length.
- Slicing a list or tuple creates a new collection object, while slicing a string creates a new string value.
Sequence positions
A sequence is an ordered collection that Python can address by position. On PCEP, the most important sequences are strings, lists, and tuples. They do not store the same kind of data, and they do not have the same mutability rules, but they all support len(), indexing, slicing, iteration, and membership tests.
The first element is at index 0. If a sequence has four elements, its valid positive indexes are 0, 1, 2, and 3; index 4 is already past the end. Negative indexes count backward from the right side. -1 means the last element, -2 means the next-to-last element, and so on.
items = ['a', 'b', 'c', 'd']
print(items[0]) # a
print(items[-1]) # d
print(items[-3]) # b
Slice shape
A slice has the form sequence[start:stop:step]. The start value is included. The stop value is excluded. That stop exclusion is not a special case; it is the core rule. items[1:3] selects positions 1 and 2, not position 3.
| Expression | Result | Why |
|---|---|---|
items[1:3] | ['b', 'c'] | Start at 1, stop before 3 |
items[:2] | ['a', 'b'] | Default start is the beginning |
items[2:] | ['c', 'd'] | Default stop is the end |
items[::2] | ['a', 'c'] | Step by 2 |
items[::-1] | ['d', 'c', 'b', 'a'] | Negative step walks backward |
For code-tracing questions, write the indexes above the values. That makes stop exclusion visible and prevents off-by-one guessing.
Index errors versus slice tolerance
Indexing asks for one position. If that position does not exist, Python raises IndexError.
letters = ['p', 'y']
print(letters[2]) # IndexError
Slicing asks for a range, and Python adjusts range boundaries that fall outside the sequence. That means letters[0:10] returns the available elements instead of raising an error. This difference is a common exam trap.
letters = ['p', 'y']
print(letters[0:10]) # ['p', 'y']
What slicing returns
A slice returns a new object containing the selected elements. For a list, the result is a new list. For a tuple, it is a new tuple. For a string, it is a new string. This matters because later list mutation affects the original list object, not a separate slice copy.
numbers = [10, 20, 30]
part = numbers[:2]
part[0] = 99
print(numbers) # [10, 20, 30]
print(part) # [99, 20]
Do not overgeneralize this into deep copying. A slice of a list makes a new outer list, but if the elements are themselves mutable objects, both lists can still refer to the same inner objects.
PCEP tracing checklist
When a question shows data[a:b:c], identify four facts before choosing an answer:
- What is the sequence length?
- Are any indexes negative?
- Which positions are included before the excluded stop?
- Does the step skip or reverse positions?
- Is the operation indexing one element or slicing a range?
That checklist catches most sequence-boundary questions. It also builds the habit needed for range(), where the stop value is excluded in the same style.
What is the result of this code? nums = [0, 1, 2, 3, 4]; print(nums[1:4])
Which expression selects the last element of a non-empty sequence named data?
What happens when Python evaluates ['x', 'y'][0:10]?