Iterating Through Tuples, Lists, and Dictionaries with a While Loop

🔁 Iterating Through Tuples, Lists, and Dictionaries with a While Loop

A for loop walks a collection for you — grab the next item, run the body, repeat until there isn't one left. A while loop does none of that bookkeeping automatically: you track where you are, you decide when to stop, and you remember to move forward each time. That's more manual work, but it's also more flexible — a while loop can keep going until a condition becomes false for reasons that have nothing to do with "reached the end of the list," which is exactly what makes it the right tool for interactive, sentinel-controlled input loops like this lesson's second example.

⚡ Coming from Java / JavaScript Java and JavaScript's C-style for (init; condition; increment) loop bundles all three parts on one line, which is exactly why it's so hard to forget the increment — it's sitting right there next to the condition. Python's while loop has no such structure: the initialisation happens before the loop, the condition is checked at the top, and the increment is just another statement somewhere in the body — easy to place, and just as easy to accidentally leave out. Every "infinite loop" bug in this lesson traces back to that one structural difference.

🧱 The Anatomy of a Manual Loop

Every while loop that walks a collection needs the same three ingredients — and it's the programmer's job to supply all three, not the language's.

1 — Initialise
i = 0
Set up whatever variable tracks your position — an index into a list/tuple, or nothing at all if you're looping on a different kind of condition entirely (like "the user hasn't typed stop yet").
i = 0 index = 0 found = False # a flag, not a counter — still "state"
2 — Condition
while i < len(items):
Checked before every iteration, including the very first. If it's false on entry, the loop body never runs at all — unlike a do...while in Java/JS, which Python doesn't have.
while i < len(items): ... while True: # deliberately infinite — ... # must break from inside
3 — Advance
i += 1
Something in the body must change, eventually making the condition false. Forget this single line and the loop runs forever — this is the single most common bug in this whole topic.
while i < len(items): print(items[i]) i += 1 # ← miss this, loop forever
break vs continue
Exit now / skip to re-check
break exits the closest enclosing loop immediately — no more condition checks. continue jumps straight back to the condition check, skipping whatever's left in the body for this iteration only.
while True: answer = input("stop to quit: ") if answer == "stop": break # leaves the while True loop if answer == "": continue # re-prompts, skips the rest

📖 Building a Dictionary with a Counting While Loop

The simplest shape: an index that starts somewhere other than zero, an upper bound, and a dictionary that grows by one key per iteration.

From Class: Building { "number20": 20, ..., "number40": 40 }
The loop variable i does double duty here — it's both the counter that controls the loop and the source of each key and value.
d = {} i = 20 while i < 41: key = f"number{i}" value = i d[key] = value i += 1 print(d) # → {'number20': 20, 'number21': 21, ..., 'number40': 40}
Why the Condition Is < 41, Not < 40
This is the same off-by-one reasoning you'd apply to any bounded loop — the difference is Python's while gives you no scaffolding to lean on, so it's worth spelling out explicitly.
# The task wants keys number20 through number40 inclusive — 21 keys total. # i starts at 20. The loop must still run when i == 40 (the last one wanted), # so the condition has to still be True at i == 40, and False at i == 41. # "i < 41" is exactly that boundary. "i < 40" would stop one key short — # a classic off-by-one, easy to miss because the loop still runs fine, # it just quietly produces the wrong result rather than crashing.

⌨️ Input-Driven Dictionary Building — Sentinels, break, and Nested Validation

This is the shape a for loop genuinely can't replace: there's no fixed collection to walk, no known length in advance — just "keep going until the user tells you to stop." That's a while True loop's natural home.

From Class: A Dictionary Built Entirely from User Input
Three loops are nested here, doing three different jobs: the outer while True keeps the whole thing running until "stop"; each inner while len(...) == 0 is a small validation loop that re-prompts until the user types something.
d = {} while True: key = input("Please enter a key (stop to quit): ") if key == "stop": break while len(key) == 0: key = input("Type something please!") continue value = input("Please enter a value (stop to quit): ") if value == "stop": break while len(value) == 0: value = input("Type something please!") continue d[key] = value if len(d) == 0: print("The Dictionary is empty.") else: print(d)
A Subtlety: What Is continue Actually Doing Here?
This trips people up because continue looks unnecessary — the loop would re-check the condition and loop back anyway once it hits the bottom of the block. It's not wrong, just redundant.
while len(key) == 0: key = input("Type something please!") continue # ← this is the LAST line in the loop body anyway, # so control would land back at "while len(key) == 0" # the moment this line finished, with or without continue. # continue only changes behaviour when there's code AFTER it in the body # that you want to skip. Here there isn't any — so it's harmless, but # worth recognising as a no-op rather than assuming it's doing something # subtle that "while len(key) == 0:" alone wouldn't already do.

🔬 Case Study: Deduplicating a Tuple While Preserving Order

This one was a genuinely tricky tutor exercise — a good excuse to slow down and build the solution up piece by piece, including the wrong turns a while-loop version of this problem tends to invite.

⚡ Honest Aside: This Is a For-Loop Problem If you could choose the tool freely, this exact task — "remove duplicates, keep first-seen order" — is far more naturally written with a for loop (or even a one-liner using dict.fromkeys(), since dicts preserve insertion order in modern Python). It was solved with while loops here specifically because that was today's constraint, not because while is the right tool for this job. Recognising when a problem wants a for loop instead is itself a useful skill — you'll see the for-loop version at the end of this section for comparison.
The Problem
Given a tuple that may contain repeated values, build a new tuple with the same elements in the same order, but with every duplicate after the first occurrence removed.
words_tuple = ("who", "why", "when", "who", "when", "what", "where", "whom", "who", "when", "who") # Wanted: ("who", "why", "when", "what", "where", "whom") # — first occurrence of each word, original order, no repeats

Building It Up, Step by Step

1
Start an empty result tuple, new_tuple = (), and an index to walk words_tuple one position at a time — the outer loop's job is "look at every word, once."
2
For the word at words_tuple[index], the question is: "have I already put this word into new_tuple?" Answering that means searching new_tuple — which, with only while loops available, means a second, inner loop with its own counter, new_index.
3
A found flag starts False before the inner search begins. If the inner loop ever matches words_tuple[index] == new_tuple[new_index], set found = True and break — there's no need to keep scanning once a match turns up.
4
After the inner loop finishes (whether it break-ed early or ran all the way through), check found. If it's still False, the word is new — append it with new_tuple = new_tuple + (words_tuple[index],) (a real .append() doesn't exist for tuples, since they're immutable — concatenation builds a brand-new tuple instead).
5
Increment the outer loop's index, unconditionally, every time round — regardless of whether this word turned out to be a duplicate or not. This step is easy to accidentally place somewhere it only sometimes runs, which is exactly how infinite loops happen here (see the pitfalls below).
The Full Solution
Two while loops, one nested inside the other, each with its own counter, condition, and advance step — exactly the anatomy from the top of this lesson, applied twice.
words_tuple = ("who", "why", "when", "who", "when", "what", "where", "whom", "who", "when", "who") new_tuple = () index = 0 while index < len(words_tuple): new_index = 0 found = False while new_index < len(new_tuple): if words_tuple[index] == new_tuple[new_index]: found = True break new_index += 1 if not found: new_tuple = new_tuple + (words_tuple[index],) index += 1 print(new_tuple) # → ('who', 'why', 'when', 'what', 'where', 'whom')

Pitfalls This Exercise Loves to Set

⚠ The inner increment inside the wrong branch
If new_index += 1 gets indented one level too far — inside the if block — it only runs on a match. On every non-match, new_index never changes, the inner while condition stays true forever, and the program hangs. The increment belongs at the same indentation as the if, not inside it.
⚠ Forgetting break only exits the inner loop
break here escapes the while new_index < len(new_tuple): search — it does not touch the outer while index < len(words_tuple): loop at all. The outer loop's own index += 1 still has to run afterwards, unconditionally, or the outer loop never advances either.
⚠ Indenting index += 1 inside the outer if not found
A tempting-looking but broken variant nests the outer increment inside if not found:. Once a genuine duplicate is found, found stays True, the increment is skipped, index never moves — and the outer loop spins on the same duplicate word forever.
⚠ Trying to .append() the result
Tuples have no .append() — attempting new_tuple.append(...) raises AttributeError. Building a tuple incrementally always means rebinding the name via concatenation, new_tuple = new_tuple + (x,), creating a new tuple object each time.
For Comparison: The Same Problem, the Natural Way
Worth seeing once you've built the while version by hand — this is what "use the right tool for the job" looks like once the constraint of today's lesson is lifted.
# Option A — a for loop, same explicit "have I seen this?" logic new_tuple = () for word in words_tuple: if word not in new_tuple: new_tuple = new_tuple + (word,) # Option B — dict.fromkeys() relies on dicts preserving insertion order # and automatically dropping duplicate keys — both jobs done in one call new_tuple = tuple(dict.fromkeys(words_tuple))

📋 Quick Reference — While-Loop Iteration Patterns

TaskWhile-loop patternNotes
Walk a list/tuple by indexwhile i < len(seq): ...; i += 1The bread-and-butter pattern used throughout this lesson
Walk and mutate a list in placewhile i < len(lst): lst[i] = ...; i += 1Safe — indexing a list doesn't change its length
Walk a dict's keyskeys = list(d); while i < len(keys): ...Snapshot the keys first if you plan to modify d mid-loop
Search for a value (linear scan)found = False; while ... and not found: ...The inner loop in this lesson's case study
Loop until a sentinel valuewhile True: ...; if x == "stop": breakNo fixed length — a for loop couldn't express this at all
Re-prompt until valid inputwhile len(x) == 0: x = input(...)This lesson's Example 2 validation loops
"Append" to a tuplet = t + (item,)No .append() — tuples are immutable, this makes a new tuple
Exit a nested loop earlybreakOnly exits the closest enclosing loop — the outer loop still needs its own advance step
⚠ Gotchas for Java / JavaScript Programmers:

There's no C-style for-loop scaffolding to lean on — Java/JavaScript's for (i = 0; i < n; i++) puts the increment right next to the condition, so it's hard to forget. Python's while loop separates initialisation, condition, and advance into three unrelated-looking statements, and the advance step can end up buried anywhere in the body — including, as this lesson's pitfalls show, inside the wrong if branch.

break only exits one loop, same as Java/JS — Python has no labelled break the way Java does (break outerLabel;). If you genuinely need to exit two nested loops at once, the idiomatic Python approach is a flag variable checked in the outer loop's condition, or wrapping the loops in a function and using return.

continue jumps to the condition check, not "the next iteration of a counted range" — in a while loop, if your advance step (i += 1) sits after a continue, that advance gets skipped entirely, and you're right back to an infinite loop. This is a real trap in while loops that doesn't exist the same way in a Java/JS C-style for, where the increment clause runs regardless of continue.

Python has no do...while — if you need a loop body to run at least once before its condition is ever checked, the common workaround is while True: with a break at the point the condition should apply (exactly the pattern in this lesson's Example 2).