Career upgrade: Learn practical AI skills for better jobs and higher pay.
Level up

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.
Last updated: May 2026

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.

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

Test Your Knowledge

What is the result of this code? nums = [0, 1, 2, 3, 4]; print(nums[1:4])

A
B
C
D
Test Your Knowledge

Which expression selects the last element of a non-empty sequence named data?

A
B
C
D
Test Your Knowledge

What happens when Python evaluates ['x', 'y'][0:10]?

A
B
C
D