24 min read

> "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

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, use range(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 exactly n values. 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:

  1. Initialize: Get the next value from the sequence and assign it to the loop variable.
  2. Check: Are there more values in the sequence? If no, exit the loop.
  3. Execute: Run the indented code block (the loop body).
  4. 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)

  1. What values does range(3, 8) produce?
  2. How would you use range() to produce the values 10, 20, 30, 40, 50?
  3. What does range(5, 2) produce? (Careful — this is a trick question.)

Verify

  1. 3, 4, 5, 6, 7 (starts at 3, stops before 8)
  2. range(10, 51, 10) — start at 10, stop before 51, step by 10
  3. 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 for loop 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:

  1. Check condition: Is the condition True?
  2. If True: Execute the loop body, then go back to step 1.
  3. 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 while body. If count += 1 is missing from the example above, count stays at 1 forever, and the condition count <= 5 never becomes False. 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 age is reassigned inside the loop with age = int(input(...)). From Chapter 3, recall that Python variables are name tags — when age is reassigned, the old value isn't "changed"; the name age is pointed to a new integer object. This is why age reflects 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 for vs. while decision 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 with 0. 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 step python 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 += 1

Approach 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 continue can make code harder to read. In the example above, a conditional without continue is clearer: python for num in range(1, 11): if num % 2 != 0: print(num, end=" ") Use continue when 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 else on a loop as "no break." If the loop finishes without breaking, the else runs. If break was triggered, the else is 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 break and continue both rely on an if condition — they're always inside an if block 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 while condition. 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 temporary print() 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 != 10 when x increments 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!") break Remove 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 before stop. 2. If you want values 1 through n, use range(1, n + 1). 3. If you want n iterations starting from 0, use range(n). 4. When in doubt, add a print() 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)

  1. You write while x < 10: but x starts at 10. How many times does the loop body execute?
  2. What's the difference between break and continue?
  3. 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

  1. Zero times. The condition is False from the start, so the body never executes. The while loop checks the condition before each iteration.
  2. break exits the loop entirely. continue skips the rest of the current iteration and starts the next one.
  3. 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 while loop for the main menu — the program keeps running until the user quits
  • A for loop 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

  1. The while running loop keeps the menu active. Setting running = False exits the loop on the next iteration check — this is cleaner than using break directly.
  2. Input validation loops (empty task name, invalid priority) use while to keep asking until input is valid.
  3. The for loop with range(len(tasks)) displays tasks with numbers starting at 1 (by printing i + 1).
  4. We combined if/elif/else (Chapter 4) with while and for loops (this chapter) to build something genuinely interactive.

Try It Yourself

  1. Add a fourth menu option: "4. Count Tasks" that prints how many tasks have been added.
  2. 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.)
  3. 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) and while (repeat while a condition is true).
  • range(start, stop, step) generates sequences of integers for for loops. The stop value is excluded.
  • The accumulator pattern — initialize before the loop, update inside, use after — is the fundamental technique for building results from repeated operations.
  • break exits a loop early, continue skips to the next iteration, and else on a loop runs only if no break occurred.
  • Nested loops run the inner loop to completion for each iteration of the outer loop, producing m * n total inner iterations.
  • Infinite loops happen when a while condition never becomes False. Off-by-one errors happen when range() 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 for loops with range() (one, two, and three arguments)
  • [ ] Iterate over strings and lists with for
  • [ ] Write while loops with proper termination conditions
  • [ ] Apply the accumulator pattern to sum, count, and build values
  • [ ] Use break and continue appropriately
  • [ ] Write nested loops for grid/table problems
  • [ ] Identify and fix infinite loops and off-by-one errors
  • [ ] Choose between for and while for 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.


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