Learning Goals
3 minBy the end of this lesson you can:
- Grow a list with
append()andinsert(). - Shrink it with
pop(),remove()anddel, choosing the right one. - Explain why removing items while looping over a list skips some of them.
Warm-Up · The List That Would Not Grow
5 minLesson 2 said index assignment can only replace. Here is what that means when you try to add.
queue = ["Mia", "Leo"] queue[2] = "Ana" print(queue)
Answer
Output
Traceback (most recent call last): IndexError: list assignment index out of range
Seat 2 does not exist yet, and assignment will not create it. You can build a new seat with a slice — queue[2:2] = ["Ana"] — but there is a method that says what you mean.
New Concept · Five Tools, Three Questions
12 minA queue at the canteen. People join the back, sometimes squeeze in partway, get served from the front, or leave because they were called away. Each is a different operation.
Adding
queue = ["Mia", "Leo"] queue.append("Ana") print(queue) queue.insert(0, "Teacher Rahim") print(queue) queue.extend(["Hugo", "Ella"]) print(queue)
Output
['Mia', 'Leo', 'Ana'] ['Teacher Rahim', 'Mia', 'Leo', 'Ana'] ['Teacher Rahim', 'Mia', 'Leo', 'Ana', 'Hugo', 'Ella']
append(x)— one item on the end. The one you will use most.insert(i, x)— one item at positioni, pushing the rest along.extend(other)— every item of another list, one by one.
append a list and you get a list inside a listqueue.append(["Hugo", "Ella"]) adds one item that happens to be a list, giving [..., ['Hugo', 'Ella']] with length 3, not 4. Use extend when you mean "add all of these".
Removing
queue = ["Teacher Rahim", "Mia", "Leo", "Ana", "Hugo"] served = queue.pop(0) print("Served:", served) last = queue.pop() print("Left early:", last) queue.remove("Leo") print("After remove:", queue) del queue[0] print("After del:", queue)
Output
Served: Teacher Rahim Left early: Hugo After remove: ['Mia', 'Ana'] After del: ['Ana']
Three questions decide which one you want:
- Do I need the item back?
pop()returns it;remove()anddeldo not. - Do I know the position or the value?
pop(i)anddeltake a position;remove(x)takes the value. - Am I removing a run?
del items[1:3]deletes a whole slice at once.
remove("Zara") when Zara is not there raises ValueError — check with in first. And remove only removes the first match, so a list with two "Ana" entries keeps one.
The loop trap
This is the bug the lesson exists to prevent.
numbers = [1, 2, 2, 3, 2, 4] for n in numbers: if n == 2: numbers.remove(n) print(numbers)
Output
[1, 3, 2, 4]
One 2 survived. The loop walks by position while the list shrinks underneath it: remove item 1 and everything shifts left, so the next item slides into a position the loop has already passed. Never remove from a list you are looping over. Build a new list instead, or loop over a copy with for n in numbers[:].
Why it matters. Every to-do list, queue, inventory and score table grows and shrinks. These five calls are how, and the loop trap is a bug that gives no error at all.
Worked Example · A Canteen Queue
12 minA morning at the stall, with every tool used where it fits.
# canteen_queue.py - a queue that grows and shrinks through a break queue = ["Mia", "Leo"] served = [] print("Opening queue:", queue) # Two more arrive queue.append("Ana") queue.append("Hugo") # A teacher goes to the front queue.insert(0, "Teacher Rahim") print("After arrivals:", queue) # Serve the first two - pop gives the name back so it can be recorded for turn in range(2): person = queue.pop(0) served.append(person) print("Serving:", person) # Leo is called to the office if "Leo" in queue: queue.remove("Leo") print("Leo left the queue") # A whole class joins at once queue.extend(["Ella", "Theo", "Zoe"]) print() print("Still waiting:", queue) print("Already served:", served) print(f"{len(served)} served, {len(queue)} waiting")
Output
Opening queue: ['Mia', 'Leo'] After arrivals: ['Teacher Rahim', 'Mia', 'Leo', 'Ana', 'Hugo'] Serving: Teacher Rahim Serving: Mia Leo left the queue Still waiting: ['Ana', 'Hugo', 'Ella', 'Theo', 'Zoe'] Already served: ['Teacher Rahim', 'Mia'] 2 served, 5 waiting
Each choice has a reason:
pop(0)for serving — a queue is served from the front, and the name is needed for theservedlist.remove()for Leo — we know who left, not where they stood.inbeforeremove— noValueErrorif they had already been served.extendfor the class — several people, added individually.
Nothing here removes from a list it is looping over. The for turn in range(2) loop counts turns, not items, which sidesteps the trap entirely.
Try It Yourself
13 minStart with three tasks. Add a fourth to the end, add an urgent one to the front, then finish the first task — printing which task you finished and what is left.
Hint
todo = ["homework", "tidy room", "practise piano"] todo.append("read chapter 4") todo.insert(0, "call grandma") done = todo.pop(0) print("Finished:", done)
Start with marks = [70, 85, 70, 92, 85, 70, 61]. Build a new list containing each mark only once, keeping the order of first appearance.
- Do not remove from
markswhile looping over it. - Print both lists and both lengths.
- Then write one comment saying what would go wrong if you had used
remove()inside the loop.
Mini-Challenge 🔥 · Debug: The Absentees Who Stayed
8 minZoe removes absent pupils from the register. Some absentees survive, and the script crashes at the end. Find three mistakes.
# register.py - buggy
present = ["Mia", "Leo", "Ana", "Hugo", "Ella", "Theo"]
absent = ["Leo", "Hugo", "Zara"]
for name in present:
if name in absent:
present.remove(name)
present.append(["Nia", "Marco"])
print("Present today:", present)
print("Count:", len(present))
present.remove("Zara")Answer
- Removing while looping.
present.remove(name)shrinks the list theforis walking, so the item after each removal is skipped. Loop over a copy —for name in present[:]— or build a new list. appendwith a list adds one item that is a list, so the count comes out one too low and the register shows['Nia', 'Marco']as a single entry. It should beextend.remove("Zara")raisesValueError— Zara was on the absent list but never on the register. Guard it withif "Zara" in present:.
The working version:
# register.py - fixed present = ["Mia", "Leo", "Ana", "Hugo", "Ella", "Theo"] absent = ["Leo", "Hugo", "Zara"] for name in present[:]: if name in absent: present.remove(name) present.extend(["Nia", "Marco"]) print("Present today:", present) print("Count:", len(present)) if "Zara" in present: present.remove("Zara")
Output
Present today: ['Mia', 'Ana', 'Ella', 'Theo', 'Nia', 'Marco'] Count: 6
Only the third bug announced itself. The first two produced a plausible-looking register that was simply wrong — which is why the loop trap is worth memorising.
Recap
3 minappend adds one to the end, insert adds at a position, extend adds all of another list. pop removes by position and hands the item back, remove removes the first matching value, del removes by position or slice. Never remove from a list while looping over it.
Vocabulary Card
- append
- Adds a single item to the end of the list.
- pop
- Removes the item at a position and returns it. Defaults to the last.
- remove
- Removes the first item equal to a value; raises
ValueErrorif absent. - ValueError
- Raised when a value that was asked for is not in the list.
Homework
4 minWrite library.py, tracking books borrowed from a class library.
- Start with five books on the shelf and an empty
borrowedlist. - Borrow two books — each must move from one list to the other, using
poporremoveplusappend. - Return one, moving it back.
- Add two new donated books in one call.
- Try to borrow a book that is not on the shelf, and handle it without crashing.
- Print both lists and both counts at every stage.
Sample · library.py
# library.py - books moving between two lists, nothing lost shelf = ["Village Boy", "Sejarah Tingkatan 2", "Python for Kids", "Bumi Manusia", "The Hobbit"] borrowed = [] def show(stage): """Print both lists so every change is visible.""" print(stage) print(" Shelf ", len(shelf), shelf) print(" Borrowed", len(borrowed), borrowed) print() show("At the start") # Borrow two - remove by name, add to borrowed for title in ["The Hobbit", "Python for Kids"]: if title in shelf: shelf.remove(title) borrowed.append(title) print("Borrowed:", title) else: print("Not on the shelf:", title) print() show("After borrowing") # Return one - pop gives the title back so it can be re-shelved returned = borrowed.pop(0) shelf.append(returned) print("Returned:", returned) print() show("After a return") # Two donations, added in one call shelf.extend(["Tuesdays with Morrie", "Ronggeng"]) show("After donations") # A book nobody has wanted = "Harry Potter" if wanted in shelf: shelf.remove(wanted) borrowed.append(wanted) else: print(f"Sorry, {wanted} is not in this library.")
Output
At the start Shelf 5 ['Village Boy', 'Sejarah Tingkatan 2', 'Python for Kids', 'Bumi Manusia', 'The Hobbit'] Borrowed 0 [] Borrowed: The Hobbit Borrowed: Python for Kids After borrowing Shelf 3 ['Village Boy', 'Sejarah Tingkatan 2', 'Bumi Manusia'] Borrowed 2 ['The Hobbit', 'Python for Kids'] Returned: The Hobbit After a return Shelf 4 ['Village Boy', 'Sejarah Tingkatan 2', 'Bumi Manusia', 'The Hobbit'] Borrowed 1 ['Python for Kids'] After donations Shelf 6 ['Village Boy', 'Sejarah Tingkatan 2', 'Bumi Manusia', 'The Hobbit', 'Tuesdays with Morrie', 'Ronggeng'] Borrowed 1 ['Python for Kids'] Sorry, Harry Potter is not in this library.
The totals always add to the same number until the donations arrive — a book removed from one list is added to the other in the next line. If yours ever loses a book, that pairing is where to look. The show() function is Section 1 · Functions doing its job.