NOETRION

Choose a topic

DAY 08 OF 30 · FUNCTIONS AND COLLECTIONS

Lists and mutation

Index, slice, append, and copy lists while understanding aliasing and in-place changes.

90–120 minutes suggested study timePython 3.12Not yet completed

Today's outcomes

  • Choose a list for an ordered mutable sequence.
  • Trace aliases versus shallow copies.
  • Avoid mutating a collection while iterating over it.

Before you start: Complete Day 07 and its practical challenge.

Understand the concept

A list is an ordered, mutable collection

A list preserves position and can contain repeated values. Index zero identifies its first item, and a negative index counts from the end. A slice produces a new list of selected items. Its stop boundary is excluded, matching the range convention from Day 6.

Choose a list when order, repeated observations, or later additions matter. A queue of study tasks and a sequence of session measurements are examples. A list does not enforce a record schema: it can contain inconsistent types, so your program must define what belongs in it.

Indexing and slicing have different boundary behavior. Reading an index outside the list raises IndexError. A slice whose endpoints extend beyond the list is clipped rather than raising that same error. An empty list therefore supports items[:2], but not items[0]. Check emptiness before reading a required first item.

Mutation changes an object that other names may share

Assignment such as alias = tasks does not copy a list. Both names refer to the same object. Appending through alias also changes the list visible through tasks. If you need an independent outer list, tasks.copy() or tasks[:] creates a shallow copy.

Shallow means nested objects remain shared. If a list contains another list, copying the outer list does not recursively copy the inner one. This is not a mysterious exception to assignment; it follows from both outer lists containing references to the same nested object. Use the simplest representation your contract needs, and test independence when nested mutable state matters.

List methods do not all return the modified list

append adds one item; extend adds items from an iterable. insert adds at a chosen position. pop removes and returns an item, while remove removes the first equal value. sort sorts in place and returns None. By contrast, sorted returns a new sorted list and can accept other iterable types.

Do not assign tasks = tasks.sort(): afterward tasks is None because the method returned None. Decide whether the source should change, then choose the corresponding operation. A program that silently reorders source observations may make later reports harder to reconcile.

Worked example

tasks = ["read", "code", "review"]
alias = tasks
snapshot = tasks.copy()

alias.append("test")
print("Current:", tasks)
print("Snapshot:", snapshot)
print("First two:", tasks[:2])
print("Last:", tasks[-1])

ordered = sorted(tasks)
print("Sorted copy:", ordered)
print("Original order:", tasks)
assert tasks == ["read", "code", "review", "test"]
assert snapshot == ["read", "code", "review"]
assert alias is tasks
assert snapshot is not tasks
Name Refers to Effect of alias.append
tasks Original list Gains "test"
alias Same original list Gains the same item
snapshot New shallow outer copy Does not gain a new outer item
ordered New sorted list Has alphabetical order independently

Equality and identity answer different questions here. The initial snapshot compared equal to tasks but was not the same object. After the append, their values differ too.

Here is the nested-copy case in a separate runnable example:

original = [["read"], ["code"]]
copied = original.copy()
copied[0].append("notes")
assert original[0] == ["read", "notes"]
assert copied is not original
assert copied[0] is original[0]
print("Inner list shared:", original[0])

Guided practice

  1. Predict: determine the results of tasks[1:3], tasks[:0], and tasks[::-1] for the original three-task list. Explain the exclusive stop and negative step.
  2. Modify: add two tasks with extend rather than append. Compare appending ['test', 'publish'] as one nested item with extending by those two strings.
  3. Reconstruct: remove blank task titles by building a new list during a loop. Do not remove items from the source while the loop is traversing it. Keep an original snapshot and verify it did not change.

Removing items during iteration can shift later items into positions the iterator has already passed. A program may appear to work for one blank but skip a second adjacent blank. Include adjacent rejected items in your test fixture.

Daily challenge

Implement advance_queue(tasks, new_task). Accept a list of nonempty strings and one nonempty new task. Create an independent outer copy, remove and remember its first item if present, append new_task, and return the updated list plus the removed item. Return None as the removed item for an empty queue. Do not mutate the caller's list.

Original New task Updated queue Removed
['read', 'code'] review ['code', 'review'] read
[] read ['read'] None
['read'] code ['code'] read

The nonempty-string assumption belongs to today's function contract. You will add arbitrary-input validation later. This exercise focuses on copying, queue order, and the empty boundary.

Reference solution

Compare after attempting the challenge
def advance_queue(tasks, new_task):
    updated = tasks.copy()
    removed = updated.pop(0) if updated else None
    updated.append(new_task)
    return updated, removed

source = ["read", "code"]
updated, removed = advance_queue(source, "review")
assert updated == ["code", "review"] and removed == "read"
assert source == ["read", "code"]
assert updated is not source
assert advance_queue([], "read") == (["read"], None)
assert advance_queue(["read"], "code") == (["code"], "read")
print("Queue contract passed")

Removing the first item from a list is clear for this tiny exercise. A large production queue may use collections.deque for a different performance profile. Do not turn a beginner demonstration into an unsupported claim that list-based queues are ideal for every workload.

Debug and explain

Explain why aliasing changes both names, why a shallow copy can still share inner objects, and why sort returns None. Before each mutating operation, ask which other name can see the same object. That question scales from this list lesson to shared state inside classes on Day 20.

If a function unexpectedly changes its caller's data, write one equality check against a snapshot and one identity check between references. Those checks reveal different parts of the problem. Keep the actual input that exposed the mutation.

Review and next step

Save the queue function and nested-copy demonstration. Recreate the three-name state diagram in words during your next review. Day 9 introduces tuples for fixed-position results and explains why tuple immutability does not freeze every object inside them.

Sources and verification

Original teaching examples checked with Python 3.12.14. Library behavior reviewed against these primary references on October 3, 2026. Day 26's examples also use Beautiful Soup 4.14.3.

DAILY CHECKPOINT

Check your understanding

Five questions, 20 points each. Pass at 85% or above, which requires all five correct. Feedback explains each answer; revise the lesson and retry when needed.

01. What does list.append return?

02. If b = a for a list, what do a and b refer to?

03. What does a shallow list copy duplicate?

04. What does values[1:3] include?

05. Why be cautious about deleting while iterating a list?

Review your practical work

Check these statements after running your own challenge. This is your self-review; the website does not execute or assess your Python code.

Pass the knowledge check and finish the practical self-review to save completion.