Skip to main content

Master Python Loops

Calculating read time…

In the previous lesson, you learned how Python makes decisions with if, elif, and match. Now let's teach your code to repeat itself — intelligently.

Imagine you have a basket of apples. You need to check each one. Do you pick them up one by one, or all at once?

That's the power of loops. They let you repeat tasks without repeating code. Today, you'll learn Python's three loop masters.

You'll automate tasks, process data, and write code that works while you sleep!

💡 What You'll Master Today:
  • Python for loops — iterating lists, dictionaries, and using enumerate()
  • Python while loops — and how to avoid the infinite-loop trap
  • The range() function in all three of its forms
  • Loop control: break, continue, and the rarely-taught loop else
  • Nested loops for grids and multi-dimensional data
  • A complete hands-on project: a Smart Inventory System

🔁 1. What Are Loops? Your Personal Code Repeater

Loops are programming structures that repeat a block of code multiple times. Instead of writing the same instruction 100 times, you write it once and tell Python: "Do this 100 times."

💡 DO: Think of Loops Like a Factory Assembly Line
Each item moves down the line. The same station performs the exact same action on every item. The loop is that station—consistent, reliable, and efficient.

🔂 2. Python For Loops: The Organized Counter

For loops are perfect when you know exactly how many times you want to repeat something. They work through items in a collection, one by one.

The Basic For Loop: Meeting Each Item

Let's greet every student in a class list.

📌 What this code does: walks through the students list one name at a time, temporarily storing each name in the loop variable student, and prints a greeting for every single one before moving on.
students = ["Alice", "Bob", "Charlie", "Diana"]

for student in students:
    print(f"Hello, {student}! Welcome to class.")

Output:

Hello, Alice! Welcome to class.
Hello, Bob! Welcome to class.
Hello, Charlie! Welcome to class.
Hello, Diana! Welcome to class.

The loop took the list students and went through it item by item. The variable student changes each time—first "Alice", then "Bob", and so on.

⚠️ IMPORTANT: The Loop Variable Name is Your Choice
You can name it anything: for person in students:, for x in students:, for item in students:. Choose a name that makes sense for what you're looping through!

For Loops with Dictionaries: Keys and Values

Dictionaries store data in key-value pairs. You can loop through them in different ways.

📌 What this code does: first loops over the dictionary directly, which gives you only the keys (student names). Then it loops using .items(), which unpacks each entry into a key and value pair (name and grade) in one step.
student_grades = {"Alice": 95, "Bob": 87, "Charlie": 92}

print("Just keys:")
for name in student_grades:
    print(f"Student: {name}")

print("\nKeys and values together:")
for name, grade in student_grades.items():
    print(f"{name} scored {grade} marks.")

Output:

Just keys:
Student: Alice
Student: Bob
Student: Charlie

Keys and values together:
Alice scored 95 marks.
Bob scored 87 marks.
Charlie scored 92 marks.

The .items() method is magic—it gives you both the key and value in one clean step.

The Powerful enumerate() Function: Get the Position

What if you need the item AND its position in the list? Use enumerate().

📌 What this code does: wraps the fruits list in enumerate(), which hands back both a running position number (starting at 0) and the item itself on every pass — here index + 1 converts that to a human-friendly count starting at 1.
fruits = ["Apple", "Banana", "Cherry"]

for index, fruit in enumerate(fruits):
    print(f"Fruit #{index + 1} is {fruit}")

Output:

Fruit #1 is Apple
Fruit #2 is Banana
Fruit #3 is Cherry

enumerate() adds a counter to your loop. The index + 1 makes it human-friendly (starting from 1 instead of 0).

🚫 DON'T: Modify a List While Looping Through It
If you're looping through a list and try to add or remove items inside the loop, Python can get confused. Instead, create a new list or loop through a copy.

⏳ 3. Python While Loops: The Persistent Guardian

While loops keep running as long as a condition remains true. They're perfect when you don't know how many repetitions you'll need.

The Basic While Loop: Keep Going While True

Imagine a countdown timer.

📌 What this code does: repeatedly checks "is countdown still greater than 0?" — printing and decreasing it each pass — until the condition finally becomes False, at which point the loop ends and "Blast off!" prints.
countdown = 5

while countdown > 0:
    print(f"Countdown: {countdown}")
    countdown = countdown - 1  # This is crucial!

print("Blast off! 🚀")

Output:

Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1
Blast off! 🚀

The loop checks: "Is countdown greater than 0?" If yes, it runs the code block. The line countdown = countdown - 1 changes the value, moving us toward the end.

The Infinite Loop Trap (and How to Escape)

What happens if we forget to change the condition?

📌 What this code does: nothing — it's commented out on purpose. If you removed the # symbols, countdown would never change, the condition would stay True forever, and your program would freeze printing "Stuck in loop!" endlessly.
# WARNING: This would run forever if uncommented!
# countdown = 5
# while countdown > 0:
#     print("Stuck in loop!")  # We never change countdown!

This is an infinite loop—a programmer's common mistake. Always ensure something in your loop changes the condition.

⚠️ IMPORTANT: The Emergency Stop Button
If you accidentally create an infinite loop, press Ctrl + C in your terminal. This sends an interrupt signal and stops the program immediately.

Real Example: User Input Validation

While loops are excellent for checking user input until it's correct.

📌 What this code does: starts with a deliberately invalid age so the loop is guaranteed to run at least once, then keeps asking for input — validating it's all digits with .isdigit() — until a value between 0 and 120 is entered.
age = -1  # Start with an invalid age

while age < 0 or age > 120:
    user_input = input("Please enter your age (0-120): ")
    
    # First, check if it's a number
    if user_input.isdigit():
        age = int(user_input)
    else:
        print("That's not a valid number. Try again.")

print(f"Thank you! Your age is {age}.")

This loop will keep asking until the user enters a reasonable age. It combines condition checking with user interaction beautifully.

🔬 Worth knowing: .isdigit() only accepts non-negative whole numbers as text — typing "-5" or "18.5" will fail the check and trigger "not a valid number," even though a human would call those "numbers" too. If you need to accept negative or decimal input, you'd validate with a try/except around int() or float() instead (covered in the previous lesson on type casting).

🔢 4. Python Range: The Number Generator

Range() is a special function that generates a sequence of numbers. It's the perfect partner for for loops when you need to repeat something a specific number of times.

Three Ways to Use Range

1. Simple Count: range(stop)

📌 What this code does: generates the numbers 0 through 4 (five numbers total) and prints each one — range(5) always starts at 0 and stops before 5.
for i in range(5):
    print(f"Loop iteration #{i}")

Generates: 0, 1, 2, 3, 4 (5 numbers total, starting from 0)

2. Start and Stop: range(start, stop)

📌 What this code does: generates numbers starting at 3 and stopping before 8, printing each one on its own line.
for i in range(3, 8):
    print(i)

Generates: 3, 4, 5, 6, 7 (starts at 3, stops before 8)

3. With Step: range(start, stop, step)

📌 What this code does: generates numbers from 0 up to (but not including) 10, jumping by 2 each time instead of counting one by one.
for i in range(0, 10, 2):
    print(i)

Generates: 0, 2, 4, 6, 8 (counts by 2s)

💡 DO: Remember This Pattern
range(5) = 0 to 4
range(2, 6) = 2 to 5
range(1, 10, 3) = 1, 4, 7 (steps of 3)
The stop number is never included in the sequence.

Range in Action: Multiplication Table Generator

Let's create a multiplication table for the number 7.

📌 What this code does: loops i from 1 through 10, multiplying it by 7 each time, and prints one formatted line of the times table per iteration.
multiplier = 7

print(f"Multiplication Table for {multiplier}:")
print("-" * 30)

for i in range(1, 11):  # 1 through 10
    result = multiplier * i
    print(f"{multiplier} × {i} = {result}")

Output:

Multiplication Table for 7:
------------------------------
7 × 1 = 7
7 × 2 = 14
7 × 3 = 21
7 × 4 = 28
7 × 5 = 35
7 × 6 = 42
7 × 7 = 49
7 × 8 = 56
7 × 9 = 63
7 × 10 = 70

Clean, efficient, and adaptable—change the multiplier or range for any table!

🎮 5. Loop Control Statements: Break, Continue, and Else

Sometimes you need to change how loops behave mid-execution. Python gives you special commands for this.

Break: The Emergency Exit

Break immediately stops the loop, no matter what.

📌 What this code does: counts up from 1 toward 100, printing "not correct" each time, until it hits exactly 42 — at that point it prints a success message and break exits the loop immediately, so numbers 43–100 are never checked.
secret_number = 42

for guess in range(1, 101):
    if guess == secret_number:
        print(f"Found it! The secret number is {guess}")
        break  # Stop looking immediately
    print(f"Tried {guess}... not correct.")

Once we find 42, we don't need to check 43-100. Break saves us time.

Continue: The Skipper

Continue skips the rest of the current iteration and jumps to the next one.

📌 What this code does: loops through a list containing two zeroes. Whenever it hits a 0, continue immediately jumps to the next number — skipping the division line entirely — which is exactly what prevents a ZeroDivisionError.
numbers = [12, 0, 8, 3, 0, 7]

for num in numbers:
    if num == 0:
        print("Skipping zero - can't divide by it!")
        continue  # Skip to next number
    
    result = 100 / num
    print(f"100 ÷ {num} = {result:.2f}")

When we hit zero, we skip the division (which would cause an error) and move to the next number.

Loop Else: The Completion Reward

Yes, loops can have else blocks! They run only if the loop completes normally (without hitting a break).

📌 What this code does: tries every password in the list against the correct one. None of them match, so the loop finishes all its iterations without ever hitting break — which means the else block runs and reports failure.
passwords_to_try = ["123456", "password", "letmein"]
correct_password = "secret123"

for attempt in passwords_to_try:
    if attempt == correct_password:
        print("Access granted!")
        break
else:
    # This runs ONLY if the break was NOT hit
    print("Access denied. All passwords failed.")

The else acts like a safety net—it tells us when we've exhausted all options without success.

⚠️ IMPORTANT: Loop Else is Confusing at First
Think of it as: "If the loop finished its job completely (no breaks), then do this extra step." It's perfect for search operations where "not found" is a valid outcome.

🧩 6. Nested Loops: Loops Within Loops

Sometimes you need to combine loops. This is common when working with grids, tables, or multi-dimensional data.

Creating a Pattern Grid

Let's make a simple coordinate grid.

📌 What this code does: the outer loop walks through each row (0–4). For every single row, the entire inner loop runs completely — printing all 5 columns — before the outer loop moves to the next row and starts the inner loop over again.
print("Coordinate Grid:")
print("  0 1 2 3 4")  # Column headers
print("  - - - - -")

for row in range(5):  # Outer loop: rows
    print(f"{row}|", end=" ")  # Row label
    
    for col in range(5):  # Inner loop: columns
        print(f"{row},{col}", end=" ")
    
    print()  # New line after each row

Output:

Coordinate Grid:
  0 1 2 3 4
  - - - - -
0| 0,0 0,1 0,2 0,3 0,4 
1| 1,0 1,1 1,2 1,3 1,4 
2| 2,0 2,1 2,2 2,3 2,4 
3| 3,0 3,1 3,2 3,3 3,4 
4| 4,0 4,1 4,2 4,3 4,4 

The outer loop handles rows. For each row, the inner loop handles all columns. This creates a 5×5 grid.

🚫 DON'T: Over-Nest Your Loops Unnecessarily
Each level of nesting multiplies complexity. Three nested loops (25×25×25) runs 15,625 times! Always ask: "Is there a simpler way?" Sometimes list comprehensions or built-in functions are better.

📦 7. Advanced Project: Smart Inventory System

Let's build a complete system that combines all loop concepts.

📌 What this code does: runs four tasks back-to-back on the same inventory dictionary — a for loop to display every item, a for-inside-while combo to top up any item below 20 units, nested for loops to print 5 price labels per item, and a for...else search that reports whether any item is priced above $1.00.
# Smart Inventory System
inventory = {
    "Apples": {"stock": 50, "price": 0.99},
    "Bananas": {"stock": 30, "price": 0.49},
    "Oranges": {"stock": 25, "price": 0.79},
    "Milk": {"stock": 15, "price": 2.49}
}

# Task 1: Display inventory with FOR loop
print("📦 CURRENT INVENTORY")
print("=" * 40)
for item, details in inventory.items():
    print(f"{item:10} | Stock: {details['stock']:3} | Price: ${details['price']:.2f}")

# Task 2: Restock low items with WHILE loop
print("\n🔄 RESTOCKING LOW ITEMS")
print("=" * 40)
low_threshold = 20

for item in list(inventory.keys()):  # Use list() to avoid modification issues
    while inventory[item]["stock"] < low_threshold:
        print(f"{item} is low ({inventory[item]['stock']}). Adding 10 more...")
        inventory[item]["stock"] += 10

# Task 3: Generate price labels with RANGE
print("\n🏷️  GENERATING PRICE LABELS")
print("=" * 40)
labels_needed = 5  # Labels per item

for item, details in inventory.items():
    print(f"\nLabels for {item}:")
    for label_num in range(1, labels_needed + 1):
        print(f"  Label #{label_num}: {item} - ${details['price']:.2f}")

# Task 4: Find expensive items with BREAK
print("\n💰 FINDING EXPENSIVE ITEMS")
print("=" * 40)
price_limit = 1.00

for item, details in inventory.items():
    if details["price"] > price_limit:
        print(f"{item} costs ${details['price']:.2f} (over ${price_limit})")
        break  # Stop after finding first expensive item
else:
    print(f"No items over ${price_limit} found.")

print("\n✅ Inventory update complete!")

This project shows loops working together: for for organized iteration, while for conditional repetition, range for counting, and break/else for control.

🔬 Verified detail — a real formatting gotcha: we ran this project in a live interpreter. Every price prints correctly with :.2f — except the line (over ${price_limit}), which prints "over $1.0", not "$1.00", because that one spot is missing the :.2f format spec used everywhere else. Only Milk ($2.49) triggers Task 4's break, and only Milk (15 < 20) triggers a restock in Task 2 — worth confirming yourself by running it, since it's a great way to see loop logic and string formatting interact in a realistic script.

⚖️ 8. Choosing the Right Loop: Quick Guide

  • Use for when:
    • You're iterating over a known collection (list, string, dictionary).
    • You know exactly how many iterations you need.
    • You need to process each item in a sequence.
  • Use while when:
    • You're waiting for a condition to change.
    • You don't know how many iterations you'll need.
    • You're monitoring something (user input, sensor data, game state).
  • Use range() with for when:
    • You need to repeat code a specific number of times.
    • You need numerical sequences (for indexing, counting, etc.).
    • You're working with numerical patterns.
💡 DO: Practice the "Loop Transformation"
Try rewriting for loops as while loops and vice versa. This deepens your understanding of how both work. Most for loops can be written as while, but not always elegantly!

📚 9. Quick Reference Cheat Sheet

📚 Loops Cheat Sheet

For loops:

for item in my_list:           # iterate a list
    pass
for key in my_dict:             # iterate keys only
    pass
for key, value in my_dict.items():  # iterate key + value
    pass
for index, item in enumerate(my_list):  # iterate with position
    pass

While loops:

while condition:
    pass                         # runs as long as condition is True
    # something inside MUST eventually make condition False

Range:

range(5)         # 0, 1, 2, 3, 4
range(2, 6)      # 2, 3, 4, 5
range(1, 10, 3)  # 1, 4, 7

Loop control:

break       # exit the loop immediately
continue    # skip to the next iteration
else:       # (on a loop) runs only if break was NOT hit

🚧 10. Common Pitfalls (And How to Avoid Them)

❌ Pitfall 1: Forgetting to update the while condition
📌 What this code does: shows the infinite loop that results when the counter is never decremented, versus the fixed version that always makes progress toward ending.
# Bad — countdown never changes, this runs forever
countdown = 5
while countdown > 0:
    print("Stuck!")

# Good — countdown moves toward 0 every pass
countdown = 5
while countdown > 0:
    print(countdown)
    countdown -= 1
❌ Pitfall 2: Modifying a list while looping over it directly
# Risky — removing items while iterating skips elements
for item in inventory:
    del inventory[item]   # can raise RuntimeError

# Safe — loop over a copy of the keys instead
for item in list(inventory.keys()):
    del inventory[item]
❌ Pitfall 3: Confusing loop else with if else
# The 'else' here is NOT tied to an 'if' —
# it's tied to the 'for' loop itself
for attempt in passwords_to_try:
    if attempt == correct_password:
        break
else:
    print("No match found")  # runs only if 'break' never fired
❌ Pitfall 4: Inconsistent number formatting across a script
# Bad — inconsistent output: $2.49 vs $1.0
print(f"Price: ${price:.2f}")
print(f"Limit: ${price_limit}")     # missing :.2f

# Good — format every price the same way
print(f"Price: ${price:.2f}")
print(f"Limit: ${price_limit:.2f}")

🏋️ 11. Practice Exercises

✅ Exercise 1: FizzBuzz
Using a for loop and range(1, 101), print "Fizz" for multiples of 3, "Buzz" for multiples of 5, "FizzBuzz" for multiples of both, and the number otherwise.
✅ Exercise 2: Sum Until Negative
Write a while loop that keeps asking the user for numbers and adds them to a running total, stopping the moment they enter a negative number.
✅ Exercise 3: Prime Number Finder
Use nested loops and break to check whether a number is prime (hint: use the inner loop's else to confirm no divisor was found).
✅ Exercise 4: Shopping Cart Total
Given a dictionary of {item: {"price": x, "quantity": y}}, use .items() in a for loop to calculate and print the total cost.
✅ Exercise 5: Fix the Formatting Bug
Take the Smart Inventory System project from this post and fix the price_limit formatting gotcha so every dollar amount consistently shows 2 decimal places.

❓ 12. Frequently Asked Questions

What is the difference between a for loop and a while loop in Python?
A for loop iterates over a known collection (a list, string, dictionary, or range) a fixed number of times. A while loop keeps running as long as a condition stays True, which is ideal when you don't know in advance how many repetitions you'll need.

Does range() include the stop number?
No. range(start, stop) always stops before the stop value — range(1, 5) produces 1, 2, 3, 4, never 5.

What's the difference between break and continue?
break exits the entire loop immediately, skipping any remaining iterations. continue only skips the rest of the current iteration and moves on to the next one — the loop keeps running.

What does the else block on a for loop actually do?
It runs only if the loop completes all its iterations without ever hitting a break. It's commonly used in search patterns: if you find what you're looking for, you break; if the loop finishes naturally, the else reports "not found."

How do I avoid an infinite while loop?
Make sure something inside the loop body changes the variable being tested in the condition, moving it toward becoming False. If you do get stuck in one, press Ctrl+C in your terminal to force-stop the program.

Is it safe to remove items from a list while looping over it?
No — modifying a list's length while iterating over it directly can skip elements or raise errors. Loop over a copy instead, such as for item in list(my_dict.keys()):, as shown in the inventory project above.

📝 13. Summary: Your Loop Mastery Checklist ✅

You now command Python's repetition engines:

  • For Loops: Your organized iterator for collections and sequences.
  • While Loops: Your persistent guardian for uncertain repetition.
  • Range: Your number generator for counting and sequencing.
  • Loop Control: Break (exit), Continue (skip), and Else (completion).
  • Nested Loops: For multi-dimensional processing.

Ready for the next step? Continue to Python Functions, where you'll wrap this repetition logic into clean, reusable blocks of code you can call whenever you need them.

Happy looping! 🔄✨

Comments