Week 9 — Lecture Outline · Lists
Course: Introduction to Computer Science — CS1 / Programming Fundamentals in Python (CSCI 1101) · Silver Oak University (fictional sample) · Prof. Okafor
Objective covered: Objective 6 — Use Python's core collections; this week: create, index, slice, mutate, and iterate lists, and reason about aliasing vs. copying.
SLOs touched: A (write and run a correct program) · B (trace code and predict list contents; find and fix a defect)
Meeting pattern: 2 studio sessions × 75 min = 150 min. Segment minutes below total ~150; scale to your own pattern.
Every code output, traced value, and list result in this outline was produced by actually running the code in Python — not hand-traced. Run them live in class; the outputs are exact. (The mutate-while-iterating result in Segment 5 is one most people — and most chatbots — get wrong by hand; we ran it.)
Week at a Glance
| The week's big question | "How do I store and change a whole collection of values under one name — and what happens when two names point at the same list?" |
| By the end of the week, students can… | (1) create a list and read it with indexing and slicing (exclusive stop, just like strings); (2) mutate a list — nums[0] = 9 and the methods append, insert, pop, remove, sort, plus len; (3) iterate a list with for to total / average / find a max, and recognize the mutate-while-iterating gotcha; (4) reason about aliasing (b = a → one list, two names) vs. copying (b = a[:]), and use in for membership. |
| Key vocabulary | list, element/item, index, negative index, slice, exclusive stop, mutable, mutation, append, insert, pop, remove, sort, len, iterate, for ... in, mutate-while-iterating, aliasing, reference, copy, [:], list(), in (membership), is vs. ==, IndexError |
| Materials | slides (Deck 9), the week's readings + video links, a free online Python environment + Python Tutor (links in the readings), one approved chatbot (Gemini / Claude / ChatGPT) for the AI-critique moment and the tutorial |
| Timing note | 8 segments, ~150 min total. Session 1 = Segments 1–4 (~75). Session 2 = Segments 5–8 (~75). This is a studio: build the list on the projector and have students type every example into the online environment with you, then run it. |
Segment 1 — Hook & the Promise (8 min) · Session 1 opens
Hook. Put a single sticky-note on the slide labeled score. "This is everything we've done for eight weeks — one name, one value." Now put up a shoebox labeled scores with a row of cards inside: 88, 92, 79. "What if one name could hold all of them, in order, and you could add, remove, and rearrange the cards whenever you want?" That box is a list — Python's most-used container. Every real program is full of them: a list of scores, a playlist, a shopping cart, a feed of posts.
The promise (write it on the board): "By Friday you'll build a list, change it five different ways, loop over it to find the highest score — and you'll know the one trap that bites every beginner: when you write b = a, you don't get a copy, you get a second name for the same box."
Why it matters line (memory hook): "b = a is two names for one list; b = a[:] is a real copy."
Segment 2 — Creating, Indexing & Slicing a List (20 min)
Plain language first. A list is an ordered collection of values written inside square brackets, separated by commas. The values are elements (or items). Build one and check its length:
nums = [10, 20, 30]
print(nums)
print(len(nums))
Output (run-verified):
[10, 20, 30]
3
Indexing — you already know this from strings. Each element has an index starting at 0. Negative indexes count from the end (-1 is the last):
nums = [10, 20, 30]
print(nums[0])
print(nums[-1])
Output (run-verified):
10
30
"Same rule as string indexing in Week 3: the first item is index 0, not 1. Reaching past the end is the classic off-by-one error — nums[3] here would crash, and we'll meet that error in Segment 6."
Slicing — exclusive stop, same as strings. nums[1:3] means "from index 1 up to but not including index 3":
nums = [10, 20, 30, 40, 50]
print(nums[1:3])
print(nums[:2])
print(nums[2:])
Output (run-verified):
[20, 30]
[10, 20]
[30, 40, 50]
"The stop is excluded — nums[1:3] gives you indexes 1 and 2, not 3. This is the single most common slicing mistake; it's identical to string slicing, so you already practiced it. A slice gives you back a new list — it does not change the original."
Segment 3 — Mutation & List Methods (the code-along) (24 min)
Set it up: "Strings could be indexed but never changed — they're immutable. Lists are different: lists are mutable, which means you can change them in place. Open the editor; we're going to build a to-do list and push it around."
Mutation by index — rewrite a slot:
nums = [10, 20, 30]
nums[0] = 9
print(nums)
Output (run-verified):
[9, 20, 30]
"nums[0] = 9 reaches into the box and replaces the first card. The list is now permanently changed."
Code-along — build & mutate a to-do list, step by step (everyone types and runs each line):
Step 1 — start the list and append (adds to the end):
todo = ["email", "lunch"]
todo.append("gym")
print(todo)
Output:
['email', 'lunch', 'gym']
Step 2 — insert(i, x) puts an item at index i, shifting the rest right:
todo.insert(0, "coffee")
print(todo)
Output:
['coffee', 'email', 'lunch', 'gym']
Step 3 — pop() removes and returns the last item; pop(0) removes and returns the first:
done = todo.pop(0)
print(done)
print(todo)
Output:
coffee
['email', 'lunch', 'gym']
Step 4 — remove(x) deletes the first matching value (not an index):
todo.remove("gym")
print(todo)
Output:
['email', 'lunch']
Step 5 — sort() orders the list in place; len() counts it:
scores = [30, 10, 20]
scores.sort()
print(scores)
print(len(scores))
Output:
[10, 20, 30]
3
The method cheat-sheet — name them and put them on a slide:
| Method | What it does | Returns |
|---|---|---|
| append(x) | add x to the end | None (changes the list) |
| insert(i, x) | put x at index i | None |
| pop() / pop(i) | remove & return last / item at i | the removed item |
| remove(x) | delete the first x by value | None |
| sort() | order the list in place | None |
| len(list) | how many items (a function, not a method) | the count |
"Burn in one warning, straight from the Python docs: append, insert, remove, and sort return None — they change the list in place rather than handing a new one back. So nums = nums.sort() is a classic bug — it sorts the list and then throws it away by overwriting nums with None. We'll see exactly that next week's chatbots get wrong."
Segment 4 — Iterating a List: Total, Average, Max (20 min) · Session 1 closes (~75)
Set it up: "A list is most useful when you walk through every item. You already know the for loop from Week 6 — point it at a list and it hands you each element in turn."
Iterate to total and average (code-along):
scores = [80, 90, 100]
total = 0
for s in scores:
total = total + s
print(total)
print(total / len(scores))
Output (run-verified):
270
90.0
"for s in scores: runs the body once per element, with s being 80, then 90, then 100. We accumulate into total. Note the average is 90.0, a float — because / always produces a float (Week 2). If your gut said 90, that's exactly why we run it."
Iterate to find the max (worked trace — predict, then run):
scores = [80, 95, 72, 88]
biggest = scores[0]
for s in scores:
if s > biggest:
biggest = s
print(biggest)
- Trace: start
biggest = 80. Compare80>80? no.95>80? yes →biggest = 95.72>95? no.88>95? no. End:95. - Predicted output:
95. Run it. Actual output (run-verified):
95
"Start biggest at the first element, then let any larger value replace it. This 'walk and keep the best so far' pattern is everywhere — max, min, longest word, highest score."
Membership with in (quick):
scores = [80, 95, 72]
print(95 in scores)
print(60 in scores)
Output (run-verified):
True
False
"in asks a yes/no question — is this value somewhere in the list? It gives back a Boolean, perfect for an if."
Segment 5 — The Mutate-While-Iterating Gotcha (20 min) · Session 2 opens
Hook back in: "Last session you looped over a list safely. Today you'll write a loop that looks obviously correct, predict its output, and watch Python do something surprising — because changing a list while you loop over it is one of the nastiest beginner bugs, and it never crashes. It just silently gives the wrong answer."
The buggy program (put it on a slide; have the room predict first): "We want to throw out every 0 from a list of scores."
scores = [0, 0, 90, 0, 85]
for s in scores:
if s == 0:
scores.remove(0)
print(scores)
- Most of the room predicts
[90, 85]— every zero gone. (So did most chatbots when we asked.) - Run it. Actual output (run-verified):
[90, 0, 85]
"A zero survived. Why? The for loop walks by position. It looks at index 0 (a 0), removes it — and now every item shifts left by one. The loop moves on to index 1, but the item that used to be at index 1 already slid down to index 0 and got skipped. Removing while iterating makes the loop jump over neighbors."
The two clean fixes (show both, run both):
Fix 1 — loop over a copy with [:], mutate the original:
scores = [0, 0, 90, 0, 85]
for s in scores[:]:
if s == 0:
scores.remove(0)
print(scores)
Output (run-verified):
[90, 85]
Fix 2 — build a new list of the ones you want to keep (often the clearest):
scores = [0, 0, 90, 0, 85]
kept = []
for s in scores:
if s != 0:
kept.append(s)
print(kept)
Output (run-verified):
[90, 85]
"Rule of the week: don't add to or remove from a list while you're looping over it. Loop over a copy (scores[:]) or build a new list. The official Python tutorial says the same thing — it's 'simpler and safer to create a new list.'"
Misconception + cure:
- ❌ "My loop is obviously right, so the output must be [90, 85]."
✅ Cure: a loop that mutates its own list skips elements. The only way to know what it really does is to run it — your gut and a hand-trace both lie here.
Segment 6 — Spot & Fix the Bug: Aliasing + IndexError (the debugging demo) (18 min)
Set it up: "Here's a program that's supposed to keep a safe backup of a list before changing it — but the 'backup' gets wrecked too. Let's debug it."
The buggy program (put it on a slide exactly as is):
scores = [88, 92, 79]
backup = scores
backup.append(100)
print("scores:", scores)
print("backup:", backup)
Debug it out loud:
1. Predict: most say backup is [88, 92, 79, 100] and scores is the untouched [88, 92, 79].
2. Run it. Actual output (run-verified):
scores: [88, 92, 79, 100]
backup: [88, 92, 79, 100]
- The bug:
backup = scoresdid not make a copy. It madebackupa second name for the same list (this is aliasing). Appending through one name shows up under both — there's only one box. - The fix, then run to confirm — make a real copy with
[:](orlist(scores)):
scores = [88, 92, 79]
backup = scores[:]
backup.append(100)
print("scores:", scores)
print("backup:", backup)
Output (run-verified):
scores: [88, 92, 79]
backup: [88, 92, 79, 100]
Prove it with is vs ==:
a = [1, 2]
b = a
print(a is b)
c = a[:]
print(a is c)
print(a == c)
Output (run-verified):
True
False
True
"a is b asks 'same box?' — True, they're aliases. a is c is False — c is a separate box. But a == c is True — same contents. is checks identity (same object); == checks equality (same values). This is the is-vs-== distinction, and it's exactly the aliasing trap in one line."
A second quick bug (let the room solve it) — IndexError:
nums = [10, 20, 30]
print(nums[3])
Run-verified error:
IndexError: list index out of range
"Three items live at indexes 0, 1, 2 — there is no index 3. Off-by-one again. The fix is nums[2] (last item) or nums[-1]."
Segment 7 — Misconceptions Round-Up + Quick Interaction (20 min)
Name the misconceptions out loud, then cure each:
- ❌ "
b = amakes a copy of the list."
✅ Cure: it makes a second name for the same list — change one, you change both. Copy withb = a[:]orb = list(a). (The signature trap of the week.) - ❌ "
nums[1:3]includes index 3."
✅ Cure: the stop is excluded — you get indexes 1 and 2. Same as string slicing. - ❌ "I can remove items from a list while looping over it."
✅ Cure: that skips elements. Loop over a copy (for x in nums[:]) or build a new list. - ❌ "
nums = nums.sort()gives me the sorted list."
✅ Cure:sort()returnsNoneand changes the list in place, so this overwritesnumswithNone. Just writenums.sort(). - ❌ "
remove(2)removes the item at index 2."
✅ Cure:remove(x)deletes by value (the first2it finds), not by index. Usepop(2)to remove by index.
Interaction — Predict-then-Run (rapid-fire, ~8 min): put four programs on a slide; students predict each output solo (30 sec), compare with a neighbor (1 min), then you run all four. Suggested with run-verified outputs:
a = [1, 2]; b = a; b.append(3); print(a) # [1, 2, 3]
nums = [5, 3, 8]; nums.append(1); nums.sort(); print(nums) # [1, 3, 5, 8]
nums = [10, 20, 30, 40]; print(nums[1:3]) # [20, 30]
nums = [4, 4, 5]
for n in nums:
if n == 4:
nums.remove(4)
print(nums) # [4, 5] (one 4 survives!)
Tally how many the room got right — celebrate the misses on the first and last ([1, 2, 3] aliasing; [4, 5] mutate-while-iterating) as "this is why we run code."
Segment 8 — Technology Workflow + AI-Critique, Callback & Hand-off (18 min) · Session 2 closes (~75)
Technology workflow — see aliasing with your own eyes:
1. Open the free online Python environment (link in the readings). Build a list and run a method; read what changed.
2. To watch aliasing, paste into Python Tutor and click forward:
python
a = [1, 2]
b = a
b.append(3)
print(a)
You'll see both a and b draw arrows to the same list box. Then change line 2 to b = a[:] and step again — now two separate boxes appear. That picture is the lesson.
AI-critique moment (students verify, not consume):
Paste this to an approved chatbot: "In Python, what does this print?
a = [1, 2]/b = a/b.append(3)/print(a)" — and also ask "doesnums.sort()return the sorted list?"
Then check by running both yourself. Chatbots routinely claimb = amakes a copy and thatprint(a)shows[1, 2]— it's actually[1, 2, 3]. And many will saynums.sort()returns the sorted list — it returnsNone. Your job all semester: the tool drafts, you run it and judge. This week's blind spot is aliasing; catch it by running.
Callback + tease:
- Callback: "Today: create, index, and slice a list; mutate it five ways; loop it to total/average/find a max; and the two traps — mutating while iterating, and b = a aliasing. The indexing and slicing were free — you already knew them from strings."
- Tease next week: "Lists are one of four core collections. Next week we meet the other three: tuples (like a list, but immutable — it can't change), dictionaries (look things up by a key instead of a number), and sets (no duplicates, fast membership). You'll learn to pick the right one for the job."
Hand-off (the week's graded work):
- Lecture Tutorial 9 (AI tutor, share-link submission) — creating, indexing/slicing, mutating, iterating, and aliasing vs. copying.
- Quiz 9, Discussion 9 ("Explain a List (and the Aliasing Trap)"), and Assignment 9 ("Working With Lists").
- Coding Lab 9 — "Build It, Mutate It, Alias It" — build & mutate a list, run a predict-then-run table, fix the aliasing / mutate-while-iterating bug, and watch b = a alias in Python Tutor.
Instructor FAQ — Common Stumbles
| Student says / does | Quick cure |
|---|---|
"I made a backup with b = a but it changed too." |
b = a aliases — one list, two names. Copy with b = a[:] or b = list(a). |
Thinks nums[1:3] includes index 3. |
Slicing stops before the second number → indexes 1 and 2 only. Same as strings. |
| "My remove-the-zeros loop left one zero." | Mutating while iterating skips elements. Loop over nums[:] or build a new list. |
Writes nums = nums.sort() and gets None. |
sort() changes the list in place and returns None. Just write nums.sort(). |
Uses remove(2) to delete index 2. |
remove(x) deletes by value; pop(2) deletes by index. |
nums[3] on a 3-item list → crash. |
IndexError: indexes are 0–2. Use nums[2] or nums[-1]. |
Confuses is and ==. |
is = same object (same box); == = same contents. Aliases are is-equal; copies are only ==-equal. |
| "I'll just trust what the chatbot says the list contains." | This week chatbots miss aliasing constantly. Run it and read what Python prints. |
Scope flag
This outline stays within Objective 6, the lists half (create, index, slice, mutate, iterate; aliasing vs. copying; in membership; reading an IndexError). It uses only this-week and prior-week constructs — for loops (Week 6), if (Week 4), / float division (Week 2), and string-style indexing/slicing (Week 3). Tuples, dictionaries, and sets are Week 10 and only teased here; list comprehensions, del, deep copies, and nested-list data structures are out of CS1 scope. Python and its method/exception names (append, pop, IndexError, …) are referenced factually; the instructor and institution remain fictional. Every output shown was produced by running the code.
~ Prof. Okafor's edition · Fall 2026 · built with thecoursemaker.com