Intuition What this page is for
The parent note taught you the rules of the eight list methods. This page throws the whole zoo of situations at you: normal cases, empty lists, missing values, negative indices, out-of-range indices, ties during sorting, and a word problem. If you can predict the output of every example here, no exam trick can surprise you.
Link back to the parent: List methods — append, insert, remove, pop, sort, reverse, count, index .
Before working examples, let us list every case class a question about list methods can hit. Each row is one "kind of situation." Our examples below will each be tagged with the cell(s) it covers, so by the end every cell is filled .
#
Case class
What is unusual about it
Covered by
C1
Normal add (append/insert in the middle)
shifting of later items
Ex 1
C2
Insert at edges (front i=0, far past end)
clamping when i > len
Ex 2
C3
Remove by value vs pop by index
one returns None, one returns the item
Ex 3
C4
Negative index in pop
counting from the right
Ex 4
C5
Empty / degenerate list
pop() on [], index on []
Ex 5
C6
Missing value (remove/index of absent)
raises ValueError
Ex 6
C7
Out-of-range index (pop(9))
raises IndexError
Ex 6
C8
Ties & stability in sort
equal keys keep original order
Ex 7
C9
Descending / reverse interplay
sort then reverse vs reverse=True
Ex 8
C10
Query methods (count, index with duplicates)
first-occurrence rule
Ex 9
C11
Real-world word problem
translating English into method calls
Ex 10
C12
Return-value trap (x = lst.sort())
None leaks into a variable
Ex 10
The figure below is the mental model we will reuse: a row of numbered lockers (indices) with items inside. Every method is a physical action on this row.
nums = [ 10 , 20 , 30 ]
nums.append( 40 )
nums.insert( 1 , 15 )
What is nums at the end?
Forecast: guess the final list before reading on. (Where does 15 land, and where does 20, 30 go?)
Start: [10, 20, 30], indices 0,1,2, so len = 3.
Why this step? We always anchor to the current length and index layout first — every method's behaviour is defined relative to it.
append(40) places 40 at index len = 3. Now [10, 20, 30, 40].
Why this step? append adds to the end ; the end of a length-3 list is the next free slot, index 3. No item moves, so it is fast.
insert(1, 15) puts 15 at index 1. Every item at index j ≥ 1 moves right to j + 1 : 20→2, 30→3, 40→4. Result [10, 15, 20, 30, 40].
Why this step? insert must make room , so it shifts the tail. Look at the s02 figure — the green arrows show each later locker's occupant sliding one place right.
Verify: length went 3 → 4 → 5 (one add each). 15 sits between 10 and 20 ✓. Final = [10, 15, 20, 30, 40].
a = [ 1 , 2 , 3 ]
a.insert( 0 , 99 ) # front
a.insert( 100 , 7 ) # index way past the end
What is a?
Forecast: does insert(100, 7) crash? Where does 7 end up?
insert(0, 99): index 0 is the front. Everything shifts right by one: [99, 1, 2, 3].
Why this step? i = 0 is the smallest valid position; the whole list is the "tail" that must move. This is the slowest place to insert (all items shift).
insert(100, 7): index 100 is far bigger than len = 4. Python clamps it to the end — it does not raise an error. Result [99, 1, 2, 3, 7].
Why this step? Unlike pop/index, insert is forgiving: any i >= len just means "put it at the end." This is a deliberate design so insert never crashes on the upper side.
Verify: two inserts → length 3 → 4 → 5 ✓. 99 at front, 7 at back. Final = [99, 1, 2, 3, 7].
insert never raises for a large index
pop(100) crashes (IndexError) but insert(100, x) does not — it clamps. They treat "too big" oppositely. Do not assume one from the other.
box = [ 5 , 6 , 7 , 6 ]
box.remove( 6 ) # (A)
taken = box.pop( 1 ) # (B)
What is box, and what is taken?
Forecast: which 6 does remove delete? What does taken become?
remove(6) searches by value and deletes the first 6 (at index 1). Result [5, 7, 6]. It returns None (thrown away here).
Why this step? remove matches value, left-to-right, and stops at the first hit. The second 6 survives.
pop(1) removes the item at index 1 of [5, 7, 6], which is 7, and returns it. So taken = 7, and box = [5, 6].
Why this step? pop takes an index , not a value, and hands the removed item back — that is the whole difference from remove.
Verify: started length 4, two removals → length 2 ✓. taken = 7 (the value that was at index 1). Final box = [5, 6].
q = [ 11 , 22 , 33 , 44 ]
x = q.pop( - 1 )
y = q.pop( - 2 )
What are x, y, and the final q?
Forecast: negative index means counting from where?
pop(-1) = the last item. -1 means "one step from the right end." That is 44. So x = 44, q = [11, 22, 33].
Why this step? Negative indices count backwards: -1 is the last, -2 the second-last, and so on. pop() with no argument is exactly pop(-1).
Now q = [11, 22, 33]. pop(-2) is the second-from-last, which is 22. So y = 22, q = [11, 33].
Why this step? We must re-count from the current list ([11,22,33]), not the original — the list already shrank.
Verify: x = 44, y = 22, final q = [11, 33]. Length 4 → 3 → 2 ✓.
empty = []
empty.append( 1 )
empty.pop() # ok?
empty.pop() # ??
What happens on the second pop()?
Forecast: does removing from an already-empty list crash?
append(1) → [1]. Fine, any list can grow.
Why this step? append never fails on emptiness — there is always a "next slot," namely index 0.
First pop() removes and returns 1. Now empty = [] again.
Why this step? There was a last element, so pop() succeeds.
Second pop() on [] raises IndexError: pop from empty list.
Why this step? There is no last item to hand back, so pop cannot fulfil its contract. This is the degenerate case: an empty list has no valid index at all .
Verify: the program crashes on line 4 with IndexError. Everything before that ran cleanly. See Exceptions — ValueError and IndexError .
nums = [ 3 , 4 , 5 ]
nums.remove( 9 ) # (A) value 9 is absent
nums.index( 9 ) # (B)
nums.pop( 9 ) # (C) index 9 out of range
Which line crashes first, and with which error?
Forecast: ValueError or IndexError — for each line?
Line (A) remove(9): there is no value 9, so remove raises ValueError: list.remove(x): x not in list. The program stops here — later lines never run.
Why this step? remove and index both work by value ; a missing value is a ValueError.
If line (A) were removed, line (B) index(9) would raise ValueError too (same reason — searching by value, not found).
Why this step? index shares remove's value-not-found failure mode.
If only line (C) ran, pop(9) on a length-3 list raises IndexError — index 9 does not exist.
Why this step? pop fails by index , so its error is IndexError, not ValueError. The kind of error tells you whether you gave a bad value or a bad position.
Verify: as written, first crash = ValueError on line (A). Value-based methods → ValueError; index-based → IndexError. Related: Exceptions — ValueError and IndexError .
pairs = [( 2 , 'x' ), ( 1 , 'y' ), ( 2 , 'z' ), ( 1 , 'w' )]
pairs.sort( key =lambda t: t[ 0 ])
What order do the tuples end up in?
Forecast: among the two 1s and two 2s, which comes first?
key=lambda t: t[0] tells sort to compare only the first element of each tuple.
Why this step? Without a key, Python would compare whole tuples; we only want to sort by the number, keeping the letters as passengers.
Sorting by first element: the 1s come before the 2s → [(1,'y'), (1,'w'), (2,'x'), (2,'z')].
Why this step? Ascending order groups the smaller keys first.
Within equal keys, Python's sort is stable : (1,'y') came before (1,'w') in the input, so it stays before it. Same for the 2s ('x' before 'z').
Why this step? Stability means "equal elements keep their original relative order." This is a guarantee you can rely on for multi-level sorting.
Verify: final = [(1,'y'), (1,'w'), (2,'x'), (2,'z')]. The 'y'-before-'w' and 'x'-before-'z' ordering matches the input — stability confirmed. See sorted() vs list.sort() .
a = [ 4 , 2 , 4 , 1 ]
b = [ 4 , 2 , 4 , 1 ]
a.sort(); a.reverse()
b.sort( reverse = True )
Do a and b end up identical?
Forecast: are they the same list?
a.sort() → [1, 2, 4, 4]; then a.reverse() flips it → [4, 4, 2, 1].
Why this step? reverse does not sort — it only mirrors the current order. So we must sort ascending first , then flip, to reach descending.
b.sort(reverse=True) sorts directly into descending → [4, 4, 2, 1].
Why this step? The reverse=True flag reverses the comparison , giving descending in one call — cleaner and equally correct.
Verify: a == b, both [4, 4, 2, 1] ✓. Two roads, same destination.
reverse() is not "sort descending"
On [3, 1, 2], reverse() gives [2, 1, 3] — not sorted at all. It blindly mirrors. Only after sorting does reversing produce descending order.
data = [ 'a' , 'b' , 'a' , 'c' , 'a' ]
n = data.count( 'a' )
i = data.index( 'a' )
j = data.index( 'c' )
Find n, i, j.
Forecast: how many 'a', and at which index is the first one?
count('a') scans the whole list and tallies matches: positions 0, 2, 4 → n = 3.
Why this step? count looks at every element; it does not stop early.
index('a') returns the first occurrence's position → index 0, so i = 0.
Why this step? index stops at the first hit; the other 'a's are irrelevant to it.
index('c'): 'c' sits at position 3 → j = 3.
Why this step? Same first-occurrence rule; there is only one 'c'.
Verify: n = 3, i = 0, j = 3. Note count neither removes nor reorders — the list data is unchanged. Same first-occurrence idea appears in Strings — index and count methods .
A ticket queue holds waiting numbers queue = [8, 3, 8, 5]. Do the following in order:
(a) a new person takes number 8 → add to the end ;
(b) the person at the front is served → remove and record their number;
(c) print how many people still hold number 8;
(d) a careless clerk writes ordered = queue.sort() to "get a sorted copy." What is ordered?
Forecast: predict the served number, the count of 8s, and what ordered holds.
(a) "add to the end" = queue.append(8) → [8, 3, 8, 5, 8].
Why this step? End ⇒ append, the fast O(1) add (see Time complexity — append O(1) vs insert O(n) ).
(b) "front is served, record their number" = served = queue.pop(0) → served = 8, queue = [3, 8, 5, 8].
Why this step? We must both remove and keep the value, so pop (not remove), and the front is index 0.
(c) queue.count(8) on [3, 8, 5, 8] → 2.
Why this step? Two 8s remain; count tallies them.
(d) ordered = queue.sort() sorts queue in place and returns None , so ordered is None — the clerk's "sorted copy" is empty of data.
Why this step? sort() mutates and returns None. To get a real sorted copy the clerk should write ordered = sorted(queue).
Verify: served = 8; remaining count of 8 = 2; ordered is None is True; queue after (d) is the sorted [3, 5, 8, 8]. The fixed version sorted(queue) would give a new [3, 5, 8, 8] while leaving queue alone — see sorted() vs list.sort() and Mutability vs Immutability in Python .
Recall Quick self-test (reveal answers)
insert(100, x) on a length-4 list — crash or clamp? ::: Clamp — x goes to the end, no error.
pop(100) on a length-4 list — crash or clamp? ::: Crash — IndexError.
remove(9) when 9 is absent raises which error? ::: ValueError.
pop() on [] raises which error? ::: IndexError (pop from empty list).
Is Python's sort stable? ::: Yes — equal keys keep their original relative order.
x = lst.sort() sets x to what? ::: None.
Mnemonic Value-error vs index-error
"Bad value → ValueError (remove, index). Bad position → IndexError (pop)." The word in the method name that you supply wrong names the error.
List methods — append, insert, remove, pop, sort, reverse, count, index
Lists — creation and indexing
Mutability vs Immutability in Python
sorted() vs list.sort()
Tuples — why no append/sort
Time complexity — append O(1) vs insert O(n)
Strings — index and count methods
Exceptions — ValueError and IndexError