Study guide

Lists, Mutation, Methods, and Aliasing

PCEP Certified Entry-Level Python Programmer Study Guide Exam

Track your progress as you read

Finished sections get a check mark.

1 PCEP Exam Map and Study Strategy

Current PCEP-30-02 Facts How PCEP Tests Code Reading Official Resources and Ethical Prep Four-Week Study Plan and Readiness

2 Computer Programming and Python Fundamentals

1.1 Lexis, Syntax, Semantics, and Indentation 1.2 Identifiers, Keywords, Comments, and Basic Program Structure 1.3 Literals, Types, and Numeral Systems 1.4 Operators, Precedence, and Type Conversion 1.5 print(), input(), and Console Tracing

3 Control Flow

Boolean Expressions, Comparisons, and Truthiness If, Elif, Else, and Decision Trees While Loops and Sentinel Patterns For, Range, and Iteration Patterns Break, Continue, Pass, Loop Else, and Nesting

4 Data Collections

Indexing, Slicing, and Sequence Rules Lists, Mutation, Methods, and Aliasing Tuples, Immutability, and Packing Dictionaries, Keys, Membership, and Iteration Strings, Sets, and Collection Choice

5 Functions and Exceptions

def, return, None, and Call Flow Parameters, Arguments, Defaults, and Keywords Scope, Shadowing, global, and Local Errors Exception Types, try, except, else, and finally Recursion, Debugging, and Final Code Reading

Lists, Mutation, Methods, and Aliasing

Key Takeaways Explain This Topic AI

Lists are mutable, so item assignment, slice assignment, deletion, and many list methods change the existing list object.

Methods such as append, extend, insert, sort, reverse, and clear mutate the list in place and return None.

pop mutates the list and returns the removed element, while remove mutates the list and returns None.

Assignment such as second = first creates another name for the same list object rather than a copy.

Use slicing, list.copy(), or list() for a shallow copy when later top-level list changes should not affect the original.

Last updated: June 2026

Mutable sequence behavior

A list is an ordered, mutable collection written with square brackets. Ordered means positions matter and indexing/slicing work like any sequence. Mutable means the same list object can be changed after creation. PCEP uses this to test whether you can separate the list object from the variable names that refer to it. scores = [70, 80, 90]

  • scores[1] = 85
  • print(scores) # [70, 85, 90]

The assignment did not create a new list; it changed the object that scores already referenced. Lists also change through slice assignment and del . letters = ['a', 'b', 'c', 'd']

  • letters[1:3] = ['X'] # slice assignment can change length
  • print(letters) # ['a', 'X', 'd']
  • del letters[0]
  • print(letters) # ['X', 'd']

Slice assignment is powerful: the right side is iterated, so letters[1:1] = [9, 8] inserts without removing anything, and the replacement does not need to match the slice length.

Method effects and return values

Many beginner mistakes come from assuming a method returns the changed list. In Python the common in-place list methods return None ; they change the list object directly.

  • Method Mutates list? Return value Typical use
  • append(x) Yes None Add one element at the end
  • extend(iterable) Yes None Add each element from an iterable
  • insert(i, x) Yes None Insert one element before index i
  • remove(x) Yes None Remove first matching value (ValueError if absent)
  • pop() / pop(i) Yes Removed element Remove and return last (or i-th) item
  • sort() Yes None Sort the existing list
  • reverse() Yes None Reverse the existing list
  • clear() Yes None Remove all elements
  • index(x) No (read) int position Find first index of a value
  • count(x) No (read) int count Count occurrences
  • This snippet is a classic trap: values = [3, 1, 2]
  • result = values.sort()
  • print(values) # [1, 2, 3]
  • print(result) # None

If you need a new sorted list without changing the original, use the built-in sorted(values) , which returns a new list. values.sort() mutates in place and evaluates to None .

append versus extend

append() adds one object as a single element. extend() iterates through another iterable and adds each element separately. With strings the distinction is especially visible because a string iterates character by character. letters = ['a']

  • letters.append('bc')
  • print(letters) # ['a', 'bc']
  • letters = ['a']
  • letters.extend('bc')
  • print(letters) # ['a', 'b', 'c']
  • Aliasing

Assignment does not copy a list; it gives the same list another name. Both names point at one object, so mutation through either name is visible through the other. first = [1, 2]

  • second = first
  • second.append(3)
  • print(first) # [1, 2, 3]
  • To make a separate top-level list, take a shallow copy: original = [1, 2]
  • copy_a = original[:]
  • copy_b = original.copy()
  • copy_c = list(original)

A shallow copy is enough when the elements are immutable values such as numbers and strings. It is not a full solution for nested lists, because the inner lists are still shared between the copies. grid = [[1], [2]]

  • copy = grid[:]
  • copy[0].append(9)
  • print(grid) # [[1, 9], [2]]
  • Exam strategy

For list questions, label each operation as a mutation, a new-object creation, or a read, then write down the method's return value. That two-column trace instantly reveals why x = x.append(5) makes x become None , why print(stack.pop()) shows the removed element, and why two aliases see each other's mutations. Watch for + versus += : a = a + [1] builds a brand-new list, while a += [1] mutates the existing list in place (like extend ).

List construction, comprehensions, and the in-place trap

PCEP expects you to recognize several ways a list comes into existence and how each interacts with aliasing. A literal such as [1, 2, 3] builds a fresh list. The list() constructor turns any iterable into a list, so list('abc') gives ['a', 'b', 'c'] and list((1, 2)) gives [1, 2] . A list comprehension like [n * 2 for n in range(3)] produces [0, 2, 4] and is itself a brand-new list each time it runs. The reason these matter is that whether a line creates a new object or mutates an existing one determines what every alias sees afterward.

Consider the multiplication trap with nested lists: grid = [[0]] * 3 does not make three independent inner lists; it makes three references to the same inner list, so grid[0].append(9) changes all three rows to [[0, 9], [0, 9], [0, 9]] . By contrast, [[0] for _ in range(3)] builds three separate inner lists. On the exam, when you see repetition ( * ) applied to a list that contains mutable elements, immediately suspect shared references; when you see a comprehension, expect independent objects.

Finally, remember that calling a mutating method as part of a larger expression substitutes None into that expression, so sorted_first = [3, 1].sort()[0] raises TypeError because .sort() returned None and you cannot index None .