While Loops and Flow Control
James finishes a for loop that checks every note in a list. "Works fine," he says, "but I only needed the first published one. It kept going through all 200 notes after it already found the answer."
Emma nods. "A for loop visits every item. That is its job. When you need a loop that stops the moment a condition changes, you want a while loop."
A while loop repeats a block of code as long as a condition stays True. Unlike a for loop (which walks through a collection), a while loop can run an unpredictable number of times. It might run zero times, ten times, or forever if the condition never changes. That last possibility is the main danger, and this lesson teaches you how to avoid it.
Your First while Loop
A while loop checks its condition before each iteration. When the condition is False, Python exits the loop:
counter: int = 0
while counter < 3:
print(counter)
counter += 1
print("Done")
Output:
0
1
2
Done
The loop runs three times. When counter reaches 3, the condition 3 < 3 is False and Python exits the loop.
The Infinite Loop Trap
James writes his first while loop and forgets counter += 1. The terminal fills with 0 repeating endlessly. Emma tells him to press Ctrl+C.
# BUG: This loop never ends!
counter: int = 0
while counter < 3:
print(counter)
# Forgot: counter += 1
"Every while loop needs a way to stop," Emma says. "If nothing inside the loop changes the condition, it runs forever. Before you write a while loop, always ask: what changes on each pass to bring this closer to ending?"
break: Exit a Loop Early
Sometimes you want to stop a loop before the condition becomes False. The break statement exits the loop immediately:
notes: list[str] = ["draft", "draft", "published", "draft"]
found_index: int = -1
index: int = 0
while index < len(notes):
if notes[index] == "published":
found_index = index
break # Stop searching, we found it
index += 1
print(found_index)
Output:
2
Without break, the loop would check the remaining notes unnecessarily. This is the pattern James wanted.
continue: Skip to the Next Iteration
The continue statement skips the rest of the current iteration and jumps back to the condition check:
counter: int = 0
processed: list[str] = []
while counter < 5:
counter += 1
if counter == 3:
continue # Skip processing for 3
processed.append(f"item-{counter}")
print(processed)
Output:
['item-1', 'item-2', 'item-4', 'item-5']
When counter is 3, continue skips append and jumps to the condition check. Notice counter += 1 comes before continue. If it came after, the loop would skip the increment and repeat on 3 forever.
pass: The Official "Do Nothing" Statement
You have been using ... (Ellipsis) as a placeholder for empty function stubs since Chapter 49. pass is Python's official "do nothing" statement for empty blocks inside if, for, and while:
def process_note(text: str) -> None:
"""Process a note. Implementation coming later."""
pass # Placeholder, does nothing yet
Both ... and pass work as placeholders, but pass is the conventional choice inside control flow blocks.
Sentinel Values with Flag Variables
A sentinel value signals a loop to stop. Instead of counting iterations, you use a boolean flag variable that flips when a condition is met:
notes: list[str] = ["draft", "review", "published", "draft"]
found_published: bool = False
position: int = 0
while not found_published and position < len(notes):
if notes[position] == "published":
found_published = True
else:
position += 1
if found_published:
print(f"First published note at index {position}")
else:
print("No published notes found")
Output:
First published note at index 2
The flag starts False and flips to True when the target is found. This pattern is useful when the stopping condition depends on data you discover inside the loop, not on a simple counter.
Testing While Loops
Test the outcomes, not the iteration count:
def find_first_published(notes: list[str]) -> int:
"""Return the index of the first 'published' note, or -1 if none."""
found: bool = False
position: int = 0
while not found and position < len(notes):
if notes[position] == "published":
found = True
else:
position += 1
if found:
return position
return -1
def test_finds_published() -> None:
assert find_first_published(["draft", "published", "draft"]) == 1
def test_returns_negative_one_when_missing() -> None:
assert find_first_published(["draft", "draft"]) == -1
def test_empty_list() -> None:
assert find_first_published([]) == -1
def test_first_item_published() -> None:
assert find_first_published(["published", "draft"]) == 0
Four tests cover the key scenarios: found in the middle, not found, empty list, and found at the start. Run uv run pytest to verify.
PRIMM-AI+ Practice: While Loop Predictions
Predict [AI-FREE]
Part A: Will these while loops terminate? Write "terminates" or "infinite" for each, with a confidence score from 1 to 5.
# Loop 1
x: int = 10
while x > 0:
x -= 3
# Loop 2
y: int = 1
while y != 10:
y += 2
# Loop 3
z: int = 5
while z < 100:
z *= 2
Check your predictions
Loop 1: Terminates. x goes 10, 7, 4, 1, -2. At -2, x > 0 is False.
Loop 2: Infinite. y goes 1, 3, 5, 7, 9, 11, 13... It skips 10 entirely, so y != 10 is never False.
Loop 3: Terminates. z goes 5, 10, 20, 40, 80, 160. At 160, z < 100 is False.
Part B: Predict break vs continue. For each snippet, predict the output.
# Snippet 1
result: list[int] = []
i: int = 0
while i < 5:
i += 1
if i == 3:
break
result.append(i)
print(result)
# Snippet 2
result2: list[int] = []
j: int = 0
while j < 5:
j += 1
if j == 3:
continue
result2.append(j)
print(result2)
Check your predictions
Snippet 1: [1, 2]. When i becomes 3, break exits the loop before appending.
Snippet 2: [1, 2, 4, 5]. When j is 3, continue skips append but the loop keeps going.
Run
Create while_practice.py with the snippets above (add a safety break after 20 iterations to the infinite loop). Run uv run python while_practice.py and compare against your predictions.
Investigate + Modify
In Loop 2, change y != 10 to y < 10. Does it terminate now? Trace the values. Then add a break that exits when y > 10 and predict the final value of y.
Exercises
Exercise 1: Spot the Bug
This function has an infinite loop bug. Find it and fix it:
def count_drafts_before_published(notes: list[str]) -> int:
"""Count draft notes before the first published note."""
count: int = 0
index: int = 0
while index < len(notes):
if notes[index] == "published":
break
if notes[index] == "draft":
count += 1
# Bug is here: what's missing?
return count
Hint
The loop checks index < len(notes) but never changes index. What line needs adding?
Solution
Add index += 1 at the end of the loop body, inside the while:
while index < len(notes):
if notes[index] == "published":
break
if notes[index] == "draft":
count += 1
index += 1 # This was missing
Exercise 2: Write the Tests
Write collect_until_stop(words: list[str]) -> list[str] that collects words using a while loop with a flag variable. Stop when "STOP" is encountered (do not include it). If no "STOP", return all words. Write four tests: "STOP" in the middle, "STOP" first, no "STOP", and empty list.
Starter code
def collect_until_stop(words: list[str]) -> list[str]:
"""Collect words until 'STOP' is encountered."""
result: list[str] = []
hit_stop: bool = False
index: int = 0
# Your while loop here using hit_stop as sentinel
...
return result
def test_stop_in_middle() -> None:
assert collect_until_stop(["go", "run", "STOP", "skip"]) == ["go", "run"]
# Write test_stop_first, test_no_stop, test_empty
Make [Mastery Gate]
Without looking at any examples, write a function called search_notes(notes: list[str], target: str) -> int that uses a while loop with a flag variable to find the first occurrence of target in notes. Return the index, or -1 if not found. Do not use break.
Then write four test functions covering: target found, target missing, empty list, and target at the last position.
Run uv run pytest to verify all tests pass.
In SmartNotes, a while loop with a sentinel flag is how you might scan notes for the first one matching a filter: "keep checking until I find it or run out of notes." The for loop from Lesson 2 processes every note. The while loop from this lesson finds one specific note and stops.
Try With AI
If Claude Code is not already running, open your terminal, navigate to your SmartNotes project folder, and type claude. If you need a refresher, Chapter 44 covers the setup.
Prompt 1: Check Your Understanding
I'm learning about while loops in Python. Here is my
understanding: "A while loop and a for loop do the same
thing. You can always use either one."
Is my summary accurate? What am I missing?
Did the AI explain when while is a better fit than for? Compare its answer to what Emma told James.
Prompt 2: Generate and Review
Write a Python function that uses a while loop with a flag
variable to find the first negative number in a list of
integers. Return its index, or -1 if all numbers are
positive. Use type annotations on all variables and the
return type. Include a docstring.
Review the output. Does the flag variable control the loop? Is the index always incremented (no infinite loop risk)? Are all variables type-annotated?
What you're learning: You are applying the sentinel pattern and infinite-loop checklist from this lesson to evaluate AI-generated code.
Python supports an else clause on loops. The else block runs only if the loop finishes without hitting break. Example: for n in [2,4,6]: / if n % 2 != 0: break / else: print("All even"). This is a niche idiom that many Python developers avoid. A flag variable (as taught above) is usually clearer. If you encounter loop/else in someone else's code, now you know what it means.
Key Takeaways
-
whileloops repeat until a condition becomesFalse. Use them when you do not know how many iterations you need. -
Every
whileloop needs a way to stop. If nothing changes the condition, you get an infinite loop. -
breakexits a loop immediately. Use it when you find what you need and can stop searching. -
continueskips the rest of the current iteration. The loop keeps running; only that one pass is cut short. -
passis the official placeholder for empty blocks. It serves the same role as...insideif,for, andwhile. -
Sentinel flags signal when to stop. A boolean that flips inside the loop is a clear, testable termination pattern.
Looking Ahead
You can now write loops that repeat a fixed number of times (for) and loops that repeat until a condition changes (while). In Lesson 5, you will learn to put loops inside loops (nested iteration) to work with grids, combinations, and multi-level data structures.