> "Computers are good at following instructions, but not at reading your mind."
Learning Objectives
- Write for loops to iterate over sequences and ranges
- Write while loops for condition-controlled repetition
- Use break, continue, and else clauses with loops
- Identify and fix infinite loops
- Choose between for and while loops for a given problem
In This Chapter
- Chapter Overview
- 5.1 Why Loops Matter
- 5.2 The for Loop
- 5.3 Iterating Over Sequences
- 5.4 The while Loop
- 5.5 Choosing Between for and while
- 5.6 The Accumulator Pattern
- 5.7 break, continue, and else
- 5.8 Nested Loops
- 5.9 Common Loop Pitfalls
- 5.10 Project Checkpoint: TaskFlow v0.4
- 5.11 Chapter Summary
- What's Next
- Chapter 5 Exercises → exercises.md
- Chapter 5 Quiz → quiz.md
- Case Study: The Accumulator Pattern in Real Data Analysis → case-study-01.md
- Case Study: How Search Engines Use Loops → case-study-02.md
Chapter 5: Repetition: Loops and Iteration
"Computers are good at following instructions, but not at reading your mind." — Donald Knuth
Chapter Overview
Up to now, every program you've written runs top to bottom, maybe taking a detour through an if or elif branch, and then it's done. That's fine for simple tasks. But think about what happens when you need to process 10,000 student grades, check every file in a directory, or keep a menu running until the user decides to quit. Writing 10,000 print() statements isn't just tedious — it's impossible to maintain and absurd to even attempt.
This chapter introduces loops — the programming construct that lets you say "do this thing repeatedly" without writing the same code over and over. Loops are where programming goes from "neat parlor trick" to "genuinely powerful tool." Once you can tell a computer to repeat a task, you can process datasets that would take a human weeks, validate user input until it's correct, and build interactive applications that stay running as long as the user wants.
Here's the thing about loops that experienced developers take for granted but that genuinely changes how beginners think about problems: the computer doesn't get tired, doesn't make transcription errors on iteration 9,437, and doesn't lose focus after the first hundred repetitions. That patience — that tireless, precise repetition — is what makes computation fundamentally different from manual work.
In this chapter, you will learn to:
- Write for loops to iterate over sequences and range() values
- Write while loops for condition-controlled repetition
- Apply the accumulator pattern to sum, count, and build results
- Use break, continue, and loop else for flow control
- Write nested loops for multi-dimensional problems
- Choose the right loop for a given problem
- Identify and fix infinite loops and off-by-one errors
🏃 Fast Track: If you've looped in another language, skim sections 5.1-5.3 for Python-specific syntax, then focus on 5.6 (accumulator pattern), 5.7 (loop control), and 5.10 (project checkpoint).
🔬 Deep Dive: After this chapter, try Case Study 1 (accumulator pattern in real data analysis) and Case Study 2 (how search engines use loops at massive scale).
5.1 Why Loops Matter
Let's start with a scenario. You're a teaching assistant, and you need to print a "Welcome" message for every student in a class of five:
print("Welcome, student 1!")
print("Welcome, student 2!")
print("Welcome, student 3!")
print("Welcome, student 4!")
print("Welcome, student 5!")
Output:
Welcome, student 1!
Welcome, student 2!
Welcome, student 3!
Welcome, student 4!
Welcome, student 5!
That works. But what if there are 500 students? Or 5,000? You're not going to write 5,000 nearly-identical lines. And even if you did, what happens when the message changes? You'd have to edit all 5,000 lines.
This is the problem that loops solve. A loop is a programming construct that repeats a block of code multiple times. Instead of writing 5,000 lines, you write the instruction once and tell the computer how many times to repeat it:
for i in range(1, 6):
print(f"Welcome, student {i}!")
Output:
Welcome, student 1!
Welcome, student 2!
Welcome, student 3!
Welcome, student 4!
Welcome, student 5!
Two lines instead of five. And if you need 5,000 students? Change 6 to 5001. Done.
Dr. Anika Patel runs a genomics lab. Last semester, she received 12,000 DNA sequence files from a collaborating lab — each one needing the same analysis: count nucleotide frequencies, flag sequences shorter than 200 base pairs, and compute GC-content ratios. A research assistant estimated it would take six weeks to process them manually in a spreadsheet. Dr. Patel wrote a Python script with a loop and processed all 12,000 files in under four minutes. The computer didn't skip any files, didn't miscopy any numbers, and didn't need a coffee break at file 3,000.
That's why loops matter. They transform what's possible.
🚪 Threshold Concept: Iteration Transforms What's Possible
Iteration — the act of repeating a process — is one of those ideas that fundamentally changes how you think about problems. Before you understand loops, you think about tasks in terms of "how long would this take me?" After you understand loops, you think in terms of "can I describe the pattern of what needs to happen once?" If you can describe it once, the computer can do it a million times.
Before this clicks: "Processing 10,000 records is impossible — it would take weeks." After this clicks: "Processing 10,000 records is the same as processing 1 record. I just need to describe the process once."
This is, genuinely, one of the most powerful ideas in computing.
5.2 The for Loop
Python's for loop iterates over a sequence of values, executing a block of code once for each value. The most common way to generate a sequence of numbers is with range().
5.2.1 Basic Syntax
for variable in range(count):
# code to repeat
# indented block
The loop variable (often called i, j, or a more descriptive name) takes on a new value from the sequence each time through the loop. Each pass through the loop is called an iteration.
for i in range(5):
print(f"Iteration {i}")
Output:
Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
Notice that range(5) produces the values 0 through 4 — five values total, but starting from 0, not 1. This zero-based counting is consistent with how Python indexes sequences (you'll see this again in Chapters 7 and 8).
⚠️ Common Pitfall:
range(5)gives you 0, 1, 2, 3, 4 — not 1, 2, 3, 4, 5. If you want 1 through 5, userange(1, 6). This is the most common source of confusion for beginners, and it catches even experienced programmers who switch between languages.
5.2.2 range() with Start, Stop, and Step
The range() function accepts up to three arguments:
| Call | Values Produced | Description |
|---|---|---|
range(5) |
0, 1, 2, 3, 4 | Start at 0, stop before 5 |
range(1, 6) |
1, 2, 3, 4, 5 | Start at 1, stop before 6 |
range(0, 10, 2) |
0, 2, 4, 6, 8 | Start at 0, stop before 10, step by 2 |
range(10, 0, -1) |
10, 9, 8, ..., 1 | Countdown from 10 to 1 |
range(5, 5) |
(empty) | Start equals stop — no iterations |
The pattern is range(start, stop, step) where:
- start is the first value (default: 0)
- stop is the value to stop before (never included)
- step is how much to increment each time (default: 1)
# Count even numbers from 2 to 20
for num in range(2, 21, 2):
print(num, end=" ")
print() # newline at the end
Output:
2 4 6 8 10 12 14 16 18 20
# Countdown from 5 to 1
for count in range(5, 0, -1):
print(f"{count}...")
print("Liftoff!")
Output:
5...
4...
3...
2...
1...
Liftoff!
💡 Intuition: Think of
range(start, stop)as a half-open interval in mathematics: [start, stop). The start is included, the stop is excluded. This "stop before" convention seems annoying at first, but it has a nice property:range(n)always produces exactlynvalues. No off-by-one headaches once you get used to it.
5.2.3 Visualizing the for Loop
Here's how a for loop executes, step by step. Imagine this as a flowchart:
- Initialize: Get the next value from the sequence and assign it to the loop variable.
- Check: Are there more values in the sequence? If no, exit the loop.
- Execute: Run the indented code block (the loop body).
- Repeat: Go back to step 1.
For the code for i in range(3): print(i), the execution trace is:
| Step | i |
Action | Output |
|---|---|---|---|
| 1 | 0 | Assign 0 to i, execute body |
0 |
| 2 | 1 | Assign 1 to i, execute body |
1 |
| 3 | 2 | Assign 2 to i, execute body |
2 |
| 4 | — | No more values, exit loop | — |
🔄 Check Your Understanding (try to answer before reading on)
- What values does
range(3, 8)produce?- How would you use
range()to produce the values 10, 20, 30, 40, 50?- What does
range(5, 2)produce? (Careful — this is a trick question.)
Verify
- 3, 4, 5, 6, 7 (starts at 3, stops before 8)
range(10, 51, 10)— start at 10, stop before 51, step by 10- An empty range — no values. When start > stop and step is positive (default 1), there are no values to produce. You'd need
range(5, 2, -1)to get 5, 4, 3.
5.3 Iterating Over Sequences
The for loop isn't limited to range(). It can iterate over any sequence — and in Python, many things are sequences. We'll explore lists and tuples fully in Chapter 8, but here's a preview of how for works with them.
5.3.1 Iterating Over a String
A string is a sequence of characters, so you can loop over it character by character:
word = "Python"
for char in word:
print(char, end=" ")
print()
Output:
P y t h o n
This is genuinely useful. Want to count how many vowels are in a string?
text = "Hello, World!"
vowel_count = 0
for char in text.lower():
if char in "aeiou":
vowel_count += 1
print(f"Vowels found: {vowel_count}")
Output:
Vowels found: 3
Notice how we combined a loop (from this chapter) with a conditional (from Chapter 4). This is the beginning of real programming — combining constructs you've learned into solutions for actual problems.
5.3.2 Iterating Over a List (Preview)
A list is an ordered collection of values. You'll learn about lists in depth in Chapter 8, but here's a preview because they work beautifully with for loops:
students = ["Alice", "Bob", "Charlie", "Diana"]
for student in students:
print(f"Welcome, {student}!")
Output:
Welcome, Alice!
Welcome, Bob!
Welcome, Charlie!
Welcome, Diana!
This is where the for loop really shines. You don't need to know how many students there are — the loop handles however many items the list contains.
🔗 Connection: In Chapter 1, we talked about pattern recognition as a pillar of computational thinking. Notice the pattern here: "do something with each item in a collection." You'll see this pattern hundreds of times in your programming career. The
forloop is how Python expresses it.
5.4 The while Loop
The for loop is perfect when you know in advance how many times to loop (or when you're iterating over a collection). But what about situations where you don't know how many iterations you need?
- Keep asking for a password until the user enters the correct one
- Keep reading data until you reach the end of a file
- Keep running a game until the player chooses to quit
For these situations, Python provides the while loop.
5.4.1 Basic Syntax
while condition:
# code to repeat
# as long as condition is True
The while loop checks its condition before each iteration. If the condition is True, the body executes. If it's False, the loop ends. This means a while loop might execute zero times if the condition is False from the start.
count = 1
while count <= 5:
print(f"Count: {count}")
count += 1
print("Done!")
Output:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Done!
5.4.2 Visualizing the while Loop
The flowchart for a while loop is:
- Check condition: Is the condition
True? - If True: Execute the loop body, then go back to step 1.
- If False: Skip the loop body and continue with the code after the loop.
For the countdown example, the execution trace:
| Iteration | count value |
Condition count <= 5 |
Action |
|---|---|---|---|
| 1 | 1 | True | Print "Count: 1", set count to 2 |
| 2 | 2 | True | Print "Count: 2", set count to 3 |
| 3 | 3 | True | Print "Count: 3", set count to 4 |
| 4 | 4 | True | Print "Count: 4", set count to 5 |
| 5 | 5 | True | Print "Count: 5", set count to 6 |
| 6 | 6 | False | Exit loop, print "Done!" |
⚠️ Common Pitfall: Forgetting to update the loop variable inside the
whilebody. Ifcount += 1is missing from the example above,countstays at 1 forever, and the conditioncount <= 5never becomesFalse. That's an infinite loop — the program runs forever (or until you press Ctrl+C to kill it).
5.4.3 Input Validation Pattern
One of the most common uses of while is validating user input. This is sometimes called a sentinel value pattern — the loop continues until a specific "sentinel" value indicates it should stop.
age = int(input("Enter your age (1-120): "))
while age < 1 or age > 120:
print("Invalid age. Please try again.")
age = int(input("Enter your age (1-120): "))
print(f"Your age is {age}.")
Example session:
Enter your age (1-120): -5
Invalid age. Please try again.
Enter your age (1-120): 200
Invalid age. Please try again.
Enter your age (1-120): 25
Your age is 25.
The loop keeps running until the user provides valid input. This is a pattern you'll use constantly. Notice how we ask for input before the loop (to initialize the variable) and again inside the loop (to give the user another chance).
✅ Best Practice: Always validate user input in a loop. Never trust that users will follow instructions. They'll enter "twenty-five" when you asked for a number, press Enter without typing anything, and paste their entire grocery list into your age field. Your program should handle it gracefully.
🔄 Spaced Review — Chapter 3 (Variables)
Look at the input validation code above. The variable
ageis reassigned inside the loop withage = int(input(...)). From Chapter 3, recall that Python variables are name tags — whenageis reassigned, the old value isn't "changed"; the nameageis pointed to a new integer object. This is whyagereflects the latest input each time the condition is checked.
5.5 Choosing Between for and while
Both loops can technically solve most repetition problems, but each has a natural fit:
| Situation | Best Loop | Why |
|---|---|---|
| Process each item in a collection | for |
The collection defines the iterations |
| Repeat a specific number of times | for |
Use range() to set the count |
| Repeat until a condition changes | while |
You don't know in advance how many iterations |
| Input validation (repeat until valid) | while |
User determines when the loop ends |
| Menu-driven program (repeat until quit) | while |
The user decides when to stop |
| Counting up or down by a fixed step | for |
range() handles the counting |
| Searching until something is found | while |
Might find it on the first try, might take 1,000 tries |
The short version: If you know how many times to loop (or you're iterating over a collection), use for. If you're waiting for something to happen, use while.
Elena's rule of thumb: Elena Vasquez, our data analyst from Chapter 1, puts it this way in her team's coding guide: "If I can answer 'how many times?' before the loop starts, I use for. If the answer is 'until something happens,' I use while."
Here's the same problem solved both ways — printing numbers 1 through 5:
# Using for — clean and clear
for i in range(1, 6):
print(i)
# Using while — works, but more verbose
i = 1
while i <= 5:
print(i)
i += 1
Both produce the same output. But the for version is shorter, has fewer places to introduce bugs (no need to initialize or update i manually), and communicates the intent more clearly: "do this five times." Use for when you can.
🔄 Spaced Review — Chapter 1 (Computational Thinking: Pattern Recognition)
The
forvs.whiledecision is an example of pattern recognition from Chapter 1. When you see a problem, you're recognizing which pattern of repetition it fits: "iterate over a known collection" (for) or "repeat until a condition is met" (while). The more loops you write, the faster you'll recognize which pattern applies. This is exactly how experienced programmers think — they've seen so many problems that the pattern is obvious before they write a single line of code.
5.6 The Accumulator Pattern
One of the most important patterns in programming is the accumulator pattern: using a loop to build up a result one piece at a time. You'll use this pattern in practically every program you write.
5.6.1 Summing Numbers
# Sum the numbers 1 through 100
total = 0 # Initialize the accumulator
for num in range(1, 101):
total += num # Update the accumulator
print(f"Sum of 1-100: {total}")
Output:
Sum of 1-100: 5050
The pattern has three parts:
1. Initialize the accumulator variable before the loop (e.g., total = 0)
2. Update the accumulator inside the loop (e.g., total += num)
3. Use the accumulated result after the loop (e.g., print(total))
📊 Real-World Application: The mathematician Carl Friedrich Gauss famously computed this sum as a child using a clever formula: n(n+1)/2 = 100(101)/2 = 5050. Our loop is less elegant but more general — it works even if you need to sum values that don't follow a neat mathematical pattern, like a list of student scores or monthly donations.
5.6.2 The Grade Calculator: Processing Multiple Scores
Let's build on the grade calculator from previous chapters. Now we can process multiple students:
num_students = int(input("How many students? "))
for i in range(1, num_students + 1):
score = float(input(f"Enter score for student {i}: "))
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"
print(f" Student {i}: {score} -> {grade}")
Example session:
How many students? 3
Enter score for student 1: 92
Student 1: 92.0 -> A
Enter score for student 2: 75
Student 2: 75.0 -> C
Enter score for student 3: 88
Student 3: 88.0 -> B
Now let's add accumulation to compute the class average:
num_students = int(input("How many students? "))
total_score = 0
for i in range(1, num_students + 1):
score = float(input(f"Enter score for student {i}: "))
total_score += score
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"
print(f" Student {i}: {score} -> {grade}")
average = total_score / num_students
print(f"\nClass average: {average:.1f}")
Example session:
How many students? 3
Enter score for student 1: 92
Student 1: 92.0 -> A
Enter score for student 2: 75
Student 2: 75.0 -> C
Enter score for student 3: 88
Student 3: 88.0 -> B
Class average: 85.0
5.6.3 Counting
Counting is accumulation with += 1 instead of += value:
# Count how many numbers between 1 and 100 are divisible by 7
count = 0
for num in range(1, 101):
if num % 7 == 0:
count += 1
print(f"Numbers divisible by 7: {count}")
Output:
Numbers divisible by 7: 14
5.6.4 Building Strings
You can accumulate strings too:
# Build a string of only the uppercase letters
text = "Hello World, From Python!"
uppercase_letters = ""
for char in text:
if char.isupper():
uppercase_letters += char
print(f"Uppercase letters: {uppercase_letters}")
Output:
Uppercase letters: HWFP
⚠️ Common Pitfall: Initializing the accumulator incorrectly. For summing, start with
0. For counting, start with0. For building strings, start with""(empty string). For finding maximums, start with the first value (or a very small number). Getting the initial value wrong is a subtle bug that can be hard to spot.
5.6.5 Elena's Monthly Totals
Elena Vasquez needs to compute donation totals for Harbor Community Services. She has monthly donation counts from 12 months and needs to find the total, the average, and the highest month:
# Elena's monthly donation report
months = [
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"
]
donations = [
4200, 3800, 5100, 4600,
3900, 4100, 5800, 4300,
6200, 5500, 7100, 8900
]
total = 0
highest = 0
highest_month = ""
for i in range(len(months)):
total += donations[i]
if donations[i] > highest:
highest = donations[i]
highest_month = months[i]
print(f" {months[i]:>12}: ${donations[i]:,}")
average = total / len(months)
print(f"\n Total donations: ${total:,}")
print(f" Monthly average: ${average:,.0f}")
print(f" Highest month: {highest_month} (${highest:,})")
Output:
January: $4,200
February: $3,800
March: $5,100
April: $4,600
May: $3,900
June: $4,100
July: $5,800
August: $4,300
September: $6,200
November: $7,100
December: $8,900
Total donations: $63,500
Monthly average: $5,292
Highest month: December ($8,900)
This is the accumulator pattern applied three times in one loop: summing (total), finding the maximum (highest), and tracking which month had the highest value (highest_month). One loop, three accumulations.
🧩 Productive Struggle
Before reading the next section, try this challenge: Write code to print the first 100 even numbers. (Not even numbers from 1 to 100 — the first 100 even numbers: 2, 4, 6, ..., 200.)
There are at least three different ways to do this with what you've learned so far. Try to find at least two before reading on.
Possible approaches
Approach 1:
range()with steppython for num in range(2, 201, 2): print(num)Approach 2: Loop 100 times, multiply by 2
python for i in range(1, 101): print(i * 2)Approach 3: Loop and filter with a conditional
python count = 0 num = 1 while count < 100: if num % 2 == 0: print(num) count += 1 num += 1Approach 1 is the most Pythonic. Approach 2 is perfectly good. Approach 3 works but is more complex than needed — it's the "brute force" method. All three are correct.
5.7 break, continue, and else
Python provides three additional tools for controlling loop behavior: break to exit early, continue to skip an iteration, and else to run code when the loop completes normally.
5.7.1 break: Exit the Loop Early
break immediately terminates the loop, regardless of whether the condition (in a while) is still True or there are remaining items (in a for).
# Search for a target number
target = 7
for num in range(1, 20):
if num == target:
print(f"Found {target}!")
break
print(f"Checking {num}...")
print("Search complete.")
Output:
Checking 1...
Checking 2...
Checking 3...
Checking 4...
Checking 5...
Checking 6...
Found 7!
Search complete.
Without break, the loop would continue checking numbers 8 through 19 even after finding the target. break saves time by stopping as soon as the answer is found.
5.7.2 continue: Skip to the Next Iteration
continue skips the rest of the current iteration and jumps back to the top of the loop for the next iteration.
# Print only odd numbers, skip even ones
for num in range(1, 11):
if num % 2 == 0:
continue
print(num, end=" ")
print()
Output:
1 3 5 7 9
When num is even, continue causes the loop to skip the print() and move to the next number.
⚠️ Common Pitfall: Overusing
continuecan make code harder to read. In the example above, a conditional withoutcontinueis clearer:python for num in range(1, 11): if num % 2 != 0: print(num, end=" ")Usecontinuewhen it genuinely simplifies the logic — like skipping invalid data early in a long processing block. Don't use it just because you can.
5.7.3 else on Loops
Python has an unusual feature: you can attach an else clause to a loop. The else block runs only if the loop completes without hitting a break.
# Check if a number is prime
num = 17
for i in range(2, num):
if num % i == 0:
print(f"{num} is not prime (divisible by {i})")
break
else:
print(f"{num} is prime!")
Output:
17 is prime!
If num were 15, the loop would find that 15 % 3 == 0, print "15 is not prime," and break. The else would not execute. But for 17, no divisor is found, the loop ends naturally (exhausts all values), and the else runs.
💡 Intuition: Think of the
elseon a loop as "no break." If the loop finishes without breaking, theelseruns. Ifbreakwas triggered, theelseis skipped. Many Python developers find this confusing and avoid it. Use it when it genuinely clarifies intent (like the prime-checking example), but don't feel pressured to use it everywhere.
5.7.4 When to Use Each
| Tool | Use When | Anti-Pattern (Don't Do This) |
|---|---|---|
break |
You've found what you're looking for | Using break as the only way to exit a while True loop for simple cases |
continue |
You want to skip invalid/unwanted items early | Using continue when a simple if would suffice |
else |
You need different behavior for "found" vs. "not found" | Using else when a Boolean flag is clearer |
🔄 Spaced Review — Chapter 4 (Conditionals: Bridge)
Notice how
breakandcontinueboth rely on anifcondition — they're always inside anifblock within a loop. This is the interplay between conditionals (Chapter 4) and loops (this chapter): conditionals control which iterations do what, while the loop controls how many iterations happen. These two constructs together — selection and repetition — can express any computation a computer is capable of performing. That's a remarkable fact from theoretical computer science.
5.8 Nested Loops
Sometimes you need a loop inside a loop. This is called nesting, and it's essential for working with two-dimensional data — grids, tables, matrices, and patterns.
5.8.1 How Nested Loops Work
For each iteration of the outer loop, the inner loop runs to completion:
for row in range(1, 4):
for col in range(1, 4):
print(f"({row},{col})", end=" ")
print() # newline after each row
Output:
(1,1) (1,2) (1,3)
(2,1) (2,2) (2,3)
(3,1) (3,2) (3,3)
The outer loop runs 3 times. For each of those 3 times, the inner loop runs 3 times. That's 3 x 3 = 9 total iterations of the inner body. If the outer loop runs m times and the inner loop runs n times, the inner body executes m * n times.
5.8.2 Multiplication Table
A classic nested loop example — a multiplication table:
# Multiplication table (1-5)
print(" ", end="")
for col in range(1, 6):
print(f"{col:4d}", end="")
print()
print(" " + "----" * 5)
for row in range(1, 6):
print(f"{row:2d} |", end="")
for col in range(1, 6):
print(f"{row * col:4d}", end="")
print()
Output:
1 2 3 4 5
--------------------
1 | 1 2 3 4 5
2 | 2 4 6 8 10
3 | 3 6 9 12 15
4 | 4 8 12 16 20
5 | 5 10 15 20 25
5.8.3 Star Patterns
Building text patterns with nested loops is a common exercise that builds intuition:
# Right triangle
size = 5
for row in range(1, size + 1):
for col in range(row):
print("*", end="")
print()
Output:
*
**
***
****
*****
The outer loop controls which row we're on (1 through 5). The inner loop prints row number of stars. Row 1 gets 1 star, row 2 gets 2, and so on.
💡 Intuition: When you see a nested loop, think "for each X, do Y for every Z." For the multiplication table: "for each row, print the product for every column." For star patterns: "for each row, print that many stars." The outer loop sets up the context; the inner loop does the work within that context.
5.9 Common Loop Pitfalls
Loops are where many new programmers hit their first real debugging challenges. Here are the most common problems and how to fix them.
5.9.1 Infinite Loops
An infinite loop is a loop that never terminates. With while loops, this happens when the condition never becomes False:
# BUG: infinite loop — count is never updated
count = 1
while count <= 5:
print(count)
# Missing: count += 1
This will print 1 forever (or until you press Ctrl+C). The fix is simple — add count += 1 inside the loop — but finding the bug can be frustrating because the program doesn't crash. It just... keeps running.
🐛 Debugging Walkthrough: The Infinite Loop
Symptom: Your program seems to "freeze" or keeps printing the same thing.
Diagnosis steps: 1. Press Ctrl+C to stop the program. 2. Look at your
whilecondition. What variable controls it? 3. Search the loop body for where that variable is modified. 4. If it's not modified, that's your bug. 5. If it is modified, add a temporaryprint()inside the loop to show the variable's value each iteration. Is it approaching the exit condition?Common causes: - Forgot to update the counter variable - Updating the variable in the wrong direction (incrementing when you should decrement) - Condition that can never be
False(e.g.,while x != 10whenxincrements by 3 and skips 10)Quick fix technique: While debugging, add a safety counter:
python safety = 0 while some_condition: # loop body safety += 1 if safety > 10000: print("SAFETY: Breaking after 10000 iterations!") breakRemove the safety counter once the bug is fixed.
5.9.2 Off-by-One Errors
Off-by-one errors happen when your loop runs one too many or one too few times. They're so common they have their own name in the industry.
# BUG: prints 1-4 instead of 1-5
for i in range(1, 5): # Should be range(1, 6)
print(i)
🐛 Debugging Walkthrough: Off-by-One
Symptom: Your loop produces almost the right result, but misses the last item or includes an extra one.
Diagnosis: 1. Check your
range()arguments. Remember:range(start, stop)stops beforestop. 2. If you want values 1 throughn, userange(1, n + 1). 3. If you wantniterations starting from 0, userange(n). 4. When in doubt, add aprint()to show what values the loop variable takes.Prevention: Before writing a loop, ask yourself: "What should the first value be? What should the last value be? How many iterations should there be?" Then verify your
range()against those answers.
5.9.3 Modifying a Collection While Iterating
This is a trap that even intermediate programmers fall into:
# BUG: modifying a list while iterating over it
numbers = [1, 2, 3, 4, 5]
for num in numbers:
if num % 2 == 0:
numbers.remove(num)
print(numbers) # Expected: [1, 3, 5], Actual: [1, 3, 5]... maybe
This might produce the expected result, or it might skip items. The safe approach is to create a new list (you'll learn this properly in Chapter 8):
numbers = [1, 2, 3, 4, 5]
odd_numbers = []
for num in numbers:
if num % 2 != 0:
odd_numbers.append(num)
print(odd_numbers)
Output:
[1, 3, 5]
5.9.4 Using the Wrong Loop Variable
When nesting loops, it's easy to accidentally use the outer loop's variable in the inner loop or vice versa:
# BUG: using 'row' where 'col' was intended
for row in range(3):
for col in range(3):
print(row, end=" ") # Should be 'col'
print()
Output (buggy):
0 0 0
1 1 1
2 2 2
Use descriptive variable names (row, col instead of i, j) to make this kind of bug easier to spot.
🔄 Check Your Understanding (try to answer before continuing)
- You write
while x < 10:butxstarts at 10. How many times does the loop body execute?- What's the difference between
breakandcontinue?- In a nested loop where the outer loop runs 4 times and the inner loop runs 6 times, how many times does the inner loop's body execute?
Verify
- Zero times. The condition is
Falsefrom the start, so the body never executes. Thewhileloop checks the condition before each iteration.breakexits the loop entirely.continueskips the rest of the current iteration and starts the next one.- 4 x 6 = 24 times. Each of the 4 outer iterations runs the inner loop's full 6 iterations.
5.10 Project Checkpoint: TaskFlow v0.4
Time to put loops to work. In Chapters 2-4, TaskFlow could greet the user (v0.1), add a single task with a timestamp (v0.2), and set task priority with validation (v0.3). Now we'll add the ability to manage multiple tasks with a menu-driven interface.
What's New in v0.4
- A list to store tasks (preview of Chapter 8 — just using it simply here)
- A
whileloop for the main menu — the program keeps running until the user quits - A
forloop to display all tasks as a numbered list - Input validation using loops
The Code
# TaskFlow v0.4 — Menu-driven task manager with loops
# Chapter 5: Repetition: Loops and Iteration
from datetime import datetime
print("=" * 40)
print(" TaskFlow v0.4 — Task Manager")
print("=" * 40)
# Store tasks as a list (we'll learn more about lists in Ch 8)
tasks = []
# Main menu loop — runs until user chooses to quit
running = True
while running:
print("\n--- Menu ---")
print("1. Add Task")
print("2. List Tasks")
print("3. Quit")
choice = input("\nChoose an option (1-3): ").strip()
if choice == "1":
# Add a task
task_name = input("Enter task description: ").strip()
while len(task_name) == 0:
print("Task description cannot be empty.")
task_name = input("Enter task description: ").strip()
# Get priority with validation
priority = input("Priority (high/medium/low): ").strip().lower()
while priority not in ("high", "medium", "low"):
print("Invalid priority. Choose high, medium, or low.")
priority = input("Priority (high/medium/low): ").strip().lower()
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
task = f"{task_name} [{priority}] (added {timestamp})"
tasks.append(task)
print(f" Task added: {task_name}")
elif choice == "2":
# List all tasks
if len(tasks) == 0:
print("\n No tasks yet. Add one with option 1!")
else:
print(f"\n Your Tasks ({len(tasks)} total):")
print(" " + "-" * 36)
for i in range(len(tasks)):
print(f" {i + 1}. {tasks[i]}")
elif choice == "3":
print("\nGoodbye! Stay productive.")
running = False
else:
print("Invalid choice. Please enter 1, 2, or 3.")
print("TaskFlow session ended.")
Example session:
========================================
TaskFlow v0.4 — Task Manager
========================================
--- Menu ---
1. Add Task
2. List Tasks
3. Quit
Choose an option (1-3): 1
Enter task description: Finish Chapter 5 exercises
Priority (high/medium/low): high
Task added: Finish Chapter 5 exercises
--- Menu ---
1. Add Task
2. List Tasks
3. Quit
Choose an option (1-3): 1
Enter task description: Buy groceries
Priority (high/medium/low): low
Task added: Buy groceries
--- Menu ---
1. Add Task
2. List Tasks
3. Quit
Choose an option (1-3): 2
Your Tasks (2 total):
------------------------------------
1. Finish Chapter 5 exercises [high] (added 2026-03-14 10:30)
2. Buy groceries [low] (added 2026-03-14 10:31)
--- Menu ---
1. Add Task
2. List Tasks
3. Quit
Choose an option (1-3): 3
Goodbye! Stay productive.
TaskFlow session ended.
What to Notice
- The
while runningloop keeps the menu active. Settingrunning = Falseexits the loop on the next iteration check — this is cleaner than usingbreakdirectly. - Input validation loops (empty task name, invalid priority) use
whileto keep asking until input is valid. - The
forloop withrange(len(tasks))displays tasks with numbers starting at 1 (by printingi + 1). - We combined
if/elif/else(Chapter 4) withwhileandforloops (this chapter) to build something genuinely interactive.
Try It Yourself
- Add a fourth menu option: "4. Count Tasks" that prints how many tasks have been added.
- Add a "Delete Task" option that asks for a task number and removes it from the list. (Hint: lists have a
.pop(index)method — we'll cover this properly in Chapter 8.) - Modify the task listing to show a different symbol next to high-priority tasks (like
[!]).
5.11 Chapter Summary
Key Concepts
- A loop repeats a block of code. Python has two kinds:
for(iterate over a sequence) andwhile(repeat while a condition is true). range(start, stop, step)generates sequences of integers forforloops. Thestopvalue is excluded.- The accumulator pattern — initialize before the loop, update inside, use after — is the fundamental technique for building results from repeated operations.
breakexits a loop early,continueskips to the next iteration, andelseon a loop runs only if nobreakoccurred.- Nested loops run the inner loop to completion for each iteration of the outer loop, producing
m * ntotal inner iterations. - Infinite loops happen when a
whilecondition never becomesFalse. Off-by-one errors happen whenrange()arguments are slightly wrong.
Key Terms Summary
| Term | Definition |
|---|---|
| Loop | A programming construct that repeats a block of code |
| Iteration | One pass through the loop body |
for loop |
A loop that iterates over a sequence of values |
while loop |
A loop that repeats while a condition is True |
range() |
A function that generates a sequence of integers |
break |
A statement that immediately exits the enclosing loop |
continue |
A statement that skips to the next iteration of the loop |
| Infinite loop | A loop that never terminates because its exit condition is never met |
| Loop variable | The variable that takes a new value each iteration of a for loop |
| Accumulator pattern | Initializing a variable before a loop and updating it each iteration to build a result |
| Sentinel value | A special value that signals the end of input or a loop |
| Counter-controlled loop | A loop that runs a predetermined number of times (typically a for with range()) |
What You Should Be Able to Do
- [ ] Write
forloops withrange()(one, two, and three arguments) - [ ] Iterate over strings and lists with
for - [ ] Write
whileloops with proper termination conditions - [ ] Apply the accumulator pattern to sum, count, and build values
- [ ] Use
breakandcontinueappropriately - [ ] Write nested loops for grid/table problems
- [ ] Identify and fix infinite loops and off-by-one errors
- [ ] Choose between
forandwhilefor a given problem
What's Next
In Chapter 6: Functions: Building Reusable Code, you'll learn to package code into reusable, named blocks. Instead of copying and pasting the same loop everywhere, you'll define a function once and call it wherever you need it. Functions are where your programs go from "scripts" to "real software" — and they'll transform how you think about organizing code.
Before moving on, complete the exercises and quiz to solidify your understanding of loops.