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
- Predict: determine the results of
tasks[1:3],tasks[:0], andtasks[::-1]for the original three-task list. Explain the exclusive stop and negative step. - Modify: add two tasks with extend rather than append. Compare appending
['test', 'publish']as one nested item with extending by those two strings. - 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.
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.
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.