Worked examples — for loop — iterating over sequences
Before anything, one word we will lean on constantly: a sequence is just an ordered line-up of
things you can walk through left-to-right — like beads on a string. The for loop is the finger
that touches each bead once, in order, and stops when there are no more beads.
The scenario matrix
Every for loop question is really one of these case classes. The last column tells you which
worked example below covers that cell.
| Case class | What makes it tricky | Example |
|---|---|---|
| Non-empty list | the "happy path" | Ex 1 |
| Empty sequence (degenerate) | body runs zero times | Ex 2 |
| Single-item sequence (limiting) | body runs exactly once | Ex 3 |
| String → characters | not a list, still a sequence | Ex 4 |
range counting (start/stop/step) |
half-open, negative step | Ex 5 |
| Dict — keys vs values vs items | three different loop targets | Ex 6 |
| Two sequences, unequal length | zip stops at the shorter |
Ex 7 |
| Index + value at once | enumerate unpacking |
Ex 8 |
| Nested loop (grid) | inner runs fully per outer step | Ex 9 |
| Unordered iterable (set) / custom iterator | order not guaranteed | Ex 10 |
break / continue / for-else control flow |
early exit, skip, "ran to completion?" | Ex 11 |
| Real-world word problem | translate words → loop | Ex 12 |
| Exam twist: mutate-while-looping | cursor skips items | Ex 13 |
Every numeric result below is machine-checked at the bottom of the page.
Worked examples
Statement: What does this print?
for x in [99]: # exactly one item
print(x)
print("done")Forecast: how many times does the body run?
- Loop over
[99]— Why this step? There is exactly one bead, so the body runs once withx = 99. On the next reach-in, the box is empty → "end of data → stop." - Prints
99, thendone.
Verify: the body executed 1 time; len([99]) = 1. ✓
Statement: Count the vowels in "banana".
Forecast: how many vowels? (Say the word slowly.)
count = 0
for ch in "banana":
if ch in "aeiou":
count = count + 1
print(count)for ch in "banana":— Why this step? Astris a sequence of characters, so the loop yieldsb, a, n, a, n, a— one character per pass, 6 passes total. No.split()needed.if ch in "aeiou":— Why this step?inhere checks membership: "is this character one of the vowels?" We increment only when it is. Vowel hits: positions of the threeas.
Verify: "banana" has 6 letters; vowels are a, a, a → count . ✓
range counting: forward, and a negative step
Statement: Produce (A) the numbers 2,4,6,8 and (B) a countdown 5,4,3,2,1.

range(2, 9, 2)), and an orange ✗ marks 9 as excluded. Below the
line, teal squares sit on 5, 4, 3, 2, 1 with a single leftward teal arrow showing the countdown
range(5, 0, -1), and a teal ✗ marks 0 as excluded.
Forecast: in range(2, 9, 2), does 9 appear? In range(5, 0, -1), does 0 appear?
for n in range(2, 9, 2): # (A) start=2, stop=9, step=2
print(n) # 2 4 6 8
for n in range(5, 0, -1): # (B) start=5, stop=0, step=-1
print(n) # 5 4 3 2 1range(2, 9, 2)— Why this step?range(start, stop, step)starts atstartand addsstepeach time. Thestopvalue is exclusive: the loop stops before reaching it, so9is never produced. We get . What it looks like: the burnt-orange ticks in the figure.range(5, 0, -1)— Why this step? A negative step counts down. Stop is still exclusive, so0never appears — we halt at1. What it looks like: the teal squares and the leftward teal arrow in the figure.
Verify: (A) list is [2,4,6,8], four items, none equal to 9. (B) list is [5,4,3,2,1],
five items, none equal to 0. ✓
Statement: Given ages = {"Asha": 30, "Ben": 25}, print (A) the names, (B) the sum of
ages, (C) each name: age line.
Forecast: what does for k in ages: alone give you — names or ages?
for name in ages: # (A) iterating a dict gives KEYS
print(name) # Asha, Ben
total = 0
for a in ages.values(): # (B) .values() gives the values
total = total + a
print(total) # 55
for name, age in ages.items(): # (C) .items() gives (key, value) pairs
print(name, age)for name in ages:— Why this step? Looping a dict directly yields keys only. That's the single most common dict-loop surprise; see Dictionaries — keys, values, items.ages.values()— Why this step? We wanted the numbers, so we ask the dict for its values explicitly, then accumulate:0 → 30 → 55.ages.items()— Why this step? Each item is a(key, value)pair; tuple-unpacking splits it intonameandagein one clean line.
Verify: keys are ["Asha","Ben"] (2 keys); values sum . ✓
unequal length with zip
Statement: Pair names = ["A", "B", "C"] with scores = [90, 75] and print each pair.
Forecast: does "C" get printed?
names = ["A", "B", "C"]
scores = [90, 75]
pairs = 0
for name, score in zip(names, scores):
print(name, score)
pairs = pairs + 1
print(pairs)zip(names, scores)— Why this step?zippairs the -th items and stops at the shorter sequence.scoreshas only 2 items, so"C"has no partner and is dropped.- The body runs for
(A,90)and(B,75)only — 2 passes.
Verify: min(len(names), len(scores)) = min(3, 2) = 2 iterations; "C" never printed. ✓
and value at once with enumerate
Statement: Print each medal position (1st, 2nd, 3rd) next to the athlete.
Forecast: enumerate starts counting at 0 by default — how do we make it start at 1?
athletes = ["Asha", "Ben", "Cara"]
for rank, name in enumerate(athletes, start=1):
print(rank, name) # 1 Asha / 2 Ben / 3 Caraenumerate(athletes, start=1)— Why this step?enumerateyields(index, item)pairs so we get a counter for free — no manuali += 1. Thestart=1argument shifts the counter so the first athlete is rank 1, not 0 (natural for medals).for rank, name in ...— Why this step? Tuple-unpacking splits each pair into two clean names. See enumerate and zip.
Verify: the printed lines are exactly 1 Asha, 2 Ben, 3 Cara — three lines, ranks
running in order and each glued to the right athlete. ✓
Statement: Print the multiplication results for rows 1–2 by columns 1–3, and count how many products you computed.

r * c (top row: 1, 2,
3; bottom row: 2, 4, 6). Two burnt-orange dots on the left label the outer rows r=1 and r=2.
A plum left-to-right arrow sweeps across each row, showing the inner loop running fully once per
outer row. Teal labels c=1, c=2, c=3 sit under the columns. A caption reads sum = 6 + 12 = 18,
count = 6.
Forecast: how many products total — and what is ?
count = 0
grand = 0
for r in range(1, 3): # r = 1, 2 (outer)
for c in range(1, 4): # c = 1, 2, 3 (inner)
grand = grand + r * c
count = count + 1
print(count, grand)- Outer
for r in range(1,3)— Why this step? Picks a row:r = 1, thenr = 2. - Inner
for c in range(1,4)— Why this step? For eachr, the inner loop runs completely, walkingc = 1, 2, 3. What it looks like: in the figure, the plum sweep across a row happens once per orange row-marker. - Total passes . The products are (row 1) and (row 2).
Verify: count ; grand . ✓
Statement: (A) Sum the numbers in the set {3, 1, 2}. (B) Loop over your own object
that hands back 10, 20, 30.
Forecast: for a set, is the order of visiting guaranteed? Does the sum depend on order?
# (A) a set — an UNORDERED collection of unique items
s = {3, 1, 2}
total = 0
for x in s: # order is NOT guaranteed
total = total + x
print(total) # 6, regardless of visiting order
# (B) a custom iterator: an object that defines __next__
class Counter:
def __init__(self):
self.n = 0
def __iter__(self):
return self # "I am my own iterator"
def __next__(self):
self.n += 10
if self.n > 30:
raise StopIteration # the "end of data -> stop" signal, by hand
return self.n
collected = []
for v in Counter():
collected.append(v)
print(collected) # [10, 20, 30]for x in s:over a set — Why this step? A set is an unordered iterable: it guarantees which items you visit but not the order. So you must never rely on set-loop order. Here we only add the items, and addition doesn't care about order — the total is the same however Python chooses to visit. This is why order-independent operations (summing, counting, membership) are the safe things to do with a set.class Counter— Why this step? This shows what "iterable" meant all along: any object that answers__next__(hand me the next item) and eventually raisesStopIteration(that same "end of data → stop" signal from the top of the page). Theforloop treats our object exactly like a list — proof the loop only needs the protocol, not a real sequence. Full detail in Iterators and generators.
Verify: (A) , independent of order. (B) the collected list is [10, 20, 30]. ✓
break, continue, and the for-else clause (control flow)
Statement: (i) Search nums = [4, 7, 2, 9, 5] for the first number greater than 8;
print it and stop, and if none exists print "none found". (ii) Run the same search but for
the first number greater than 100 (there is none) to see the else block fire. (iii) Separately,
sum only the even numbers using continue.
Forecast: does the else after a for run when we break, or when we don't?
nums = [4, 7, 2, 9, 5]
# (i) break + for-else: else runs ONLY if the loop was never broken
for n in nums:
if n > 8:
print("found", n) # found 9
break
else:
print("none found") # SKIPPED here, because we DID break
# (ii) same loop, threshold 100 -> nothing matches -> no break -> else runs
for n in nums:
if n > 100:
print("found", n) # never printed
break
else:
print("none found") # PRINTED here, because we never broke
# (iii) continue: skip the rest of THIS pass, jump to the next item
even_total = 0
for n in nums:
if n % 2 != 0:
continue # odd -> skip the add, go to next n
even_total = even_total + n
print(even_total) # only 4 and 2 are even -> 6break(case i) — Why this step?breaksays "I'm done, leave the loop now." As soon as we hit9(the first value over 8) we stop — no point scanning5. This is whybreakmakes searches efficient: you exit the instant you succeed.for ... else(case i) — Why this step? Theelseattached to aforruns only if the loop finished normally (never hit abreak). Since case (i) didbreak, itselseis skipped, so"none found"does not print there.for ... else(case ii) — Why this step? Here nothing exceeds 100, so the loop walks every item, never breaks, and reaches its end normally. That is exactly the condition that fires theelse, so"none found"is printed. This is the clean way to say "I searched everything and found nothing." This is the no-break case theelsewas built for.continue(case iii) — Why this step?continuesays "skip the rest of the body for this item and move to the next one." For odd numbers we skip the addition entirely; only the evens (4and2) reacheven_total. Trace:4→(skip 7)→+2→(skip 9)→(skip 5).
Verify: (i) first value is 9, and its else is not printed (loop broke). (ii)
no value , so the else is printed ("none found"). (iii) even numbers are 4, 2
so even_total . ✓
for-else runs when the loop is empty"
No. The else runs when the loop completes without a break — including, but not limited
to, when the sequence was empty. It has nothing to do with an if/else truth test. Read it
as "no-break-else."
Statement: A shop gives a 10% discount on every item over 100 rupees. (The rupee, symbol
₹, is India's currency unit — read every price below as "rupees.") Given a cart
[50, 120, 200, 80] in rupees, compute the total the customer pays.
Forecast: only two items get discounted — guess the final bill.
cart = [50, 120, 200, 80] # prices in rupees
bill = 0
for price in cart:
if price > 100:
price = price * 0.9 # apply 10% off
bill = bill + price
print(bill)for price in cart:— Why this step? We translate "for every item" straight into a loop; the cart is our sequence.if price > 100:thenprice * 0.9— Why this step? The rule only fires for items over 100 rupees.120 → 108,200 → 180;50and80untouched.- Accumulate into
bill.
Verify (units: rupees, ₹): . Two items discounted, two not. ✓
while looping it Statement: Predict the final list.
nums = [1, 2, 3, 4]
for n in nums:
if n % 2 == 0:
nums.remove(n)
print(nums)Forecast: the intent is to remove all evens → [1, 3]. Does it actually happen?
- Cursor at index 0:
n = 1(odd, keep). Cursor advances to index 1. - Index 1:
n = 2(even) →remove(2). Now the list is[1, 3, 4]— everything shifted left. But the cursor moves to index 2. Why this step matters? Index 2 now holds4, because3slid into index 1 and got skipped. - Index 2:
n = 4(even) →remove(4). List is[1, 3]. Cursor advances to index 3, which is past the end → loop stops.
Result: [1, 3] — and it looks correct by luck here, but 3 was never even
examined. Change the data and you get silent bugs.
Verify: running the loop yields [1, 3]; the safe version
[n for n in [1,2,3,4] if n % 2 != 0] also gives [1, 3], but without skipping. See
List comprehensions. ✓
feels right
"I removed the item I'm standing on, so the rest just shift — no harm." The harm is that the
iterator counts by position index; a left-shift moves an unseen item into the position
you already passed. Fix: iterate a copy for n in nums[:]: or build a new list.
Recall Which construct for which case?
Empty sequence — body runs how many times? ::: Zero. for over [] is legal and does nothing.
range(5, 0, -1) produces? ::: 5, 4, 3, 2, 1 — negative step counts down, stop (0) excluded.
Iterating a dict directly gives? ::: The keys. Use .values() or .items() otherwise.
zip over lengths 3 and 2 runs how many times? ::: 2 — it stops at the shorter sequence.
Does looping a set guarantee order? ::: No — a set is unordered; only order-independent ops are safe.
When does a for-else clause run? ::: Only when the loop finished without hitting a break.
What does continue do? ::: Skips the rest of the current pass and jumps to the next item.
Safe way to filter a list you're looping? ::: Build a new list (comprehension) or iterate a copy xs[:].
"Empty does nothing, string gives letters, range is half-open, dict gives keys, zip stops short, enumerate counts, sets ignore order, break-exits and continue-skips, for-else means no-break, and never delete the bead you're touching."
Connections
- Parent: for loop (Hinglish) — the mechanism these cases exercise.
- range function — Ex 5's half-open counting and negative steps.
- Lists and indexing — Ex 1, 13: the sequence and the position cursor.
- Strings as sequences — Ex 4: why looping a string yields characters.
- Dictionaries — keys, values, items — Ex 6: three loop targets.
- enumerate and zip — Ex 7, 8: pairing and counting cleanly.
- List comprehensions — Ex 13's safe fix.
- while loop — condition-based repetition — the desugared engine behind every case;
break/continuework there too. - Iterators and generators — Ex 2, 10:
StopIteration, sets, and custom iterators.
Concept Map
How to read this map: start at the top node "scenario matrix" — every branch below it is one case class from the table, in the same order you met the worked examples. Follow a branch down to see the one tricky idea that case teaches. The only branch that continues further is the mutate-while-looping case, whose arrow points to its fix (copy or comprehension) — a reminder that this is the one case where the naive approach is genuinely unsafe. Read it as a checklist: if you can name what each leaf means, you have covered every scenario on this page.