🌱 Beginner

Loops (for & while)

📖 Lesson 10 ⏱ 30 min 🧪 5 questions 💻 3 exercises

🎯 Learning Objectives

  • Use for loops to iterate over sequences, ranges, and dictionaries
  • Use while loops for condition-based repetition
  • Control loop execution with break, continue, and else
  • Understand range() and its parameters
  • Avoid common loop pitfalls (infinite loops, modifying during iteration)
  • Use nested loops and know when to flatten them

The for Loop

Python's for loop iterates over any iterable — lists, strings, tuples, sets, dictionaries, files, and more. It doesn't use a counter like C-style for loops; it directly gives you each item.

# Iterate over a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)

# Iterate over a string
for char in "Python":
    print(char, end=" ")  # P y t h o n

# Iterate over a dictionary
scores = {"Alice": 85, "Bob": 92}
for name, score in scores.items():
    print(f"{name}: {score}")

# Iterate over a set (order not guaranteed)
for n in {3, 1, 4, 1, 5}:
    print(n)
for_basics.py
Python's for is a for-each loop. It doesn't count — it asks the iterable for the next item each iteration. This is why it works with any iterable, not just indexed collections.

The range() Function

range() generates a sequence of numbers. It's commonly used when you need to loop a specific number of times or need numeric indices.

CallProducesNotes
range(5)0, 1, 2, 3, 4Start defaults to 0; stop is exclusive
range(2, 6)2, 3, 4, 5Start inclusive, stop exclusive
range(0, 10, 2)0, 2, 4, 6, 8Step of 2
range(10, 0, -1)10, 9, 8, …, 1Count down
# Repeat 5 times
for i in range(5):
    print(f"Iteration {i}")

# Loop with specific start/stop
for n in range(1, 11):
    print(n, end=" ")  # 1 2 3 4 5 6 7 8 9 10

# Count backwards
for n in range(5, 0, -1):
    print(n, end=" ")  # 5 4 3 2 1

# Use with len() to iterate by index (prefer enumerate instead!)
colors = ["red", "green", "blue"]
for i in range(len(colors)):
    print(f"{i}: {colors[i]}")

# Better — use enumerate
for i, color in enumerate(colors):
    print(f"{i}: {color}")
range_demo.py
range() is lazy — it doesn't create a list in memory. range(1_000_000_000) uses almost no memory because it generates numbers on demand.

The while Loop

A while loop repeats as long as its condition is True. Use it when you don't know in advance how many iterations you need.

# Basic while
count = 0
while count < 5:
    print(count)
    count += 1
# 0, 1, 2, 3, 4

# User input loop
while True:
    answer = input("Type 'quit' to exit: ")
    if answer == "quit":
        break
    print(f"You said: {answer}")

# Countdown
n = 10
while n > 0:
    print(n, end=" ")
    n -= 1
print("Liftoff!")
while_basics.py
A while loop with a condition that never becomes False creates an infinite loop. Always ensure something inside the loop will eventually make the condition False (or use break).

for vs while — when to use which

Use for when…Use while when…
You have a definite collection to iterateYou don't know how many iterations ahead of time
You want to loop a known number of timesYou're waiting for a condition to change
Processing each item in a sequenceUser input loops, polling, convergence algorithms

break and continue

StatementEffect
breakExit the loop immediately (skip remaining iterations)
continueSkip the rest of this iteration, jump to the next one
# break — stop searching once found
numbers = [4, 7, 2, 9, 1, 8]
for n in numbers:
    if n == 9:
        print("Found 9!")
        break
    print(f"Checked {n}")
# Checked 4, Checked 7, Checked 2, Found 9!

# continue — skip odd numbers
for n in range(1, 11):
    if n % 2 != 0:
        continue  # skip to next iteration
    print(n, end=" ")
# 2 4 6 8 10

# break in a while loop
attempts = 0
while True:
    password = input("Password: ")
    attempts += 1
    if password == "secret":
        print("Access granted!")
        break
    if attempts >= 3:
        print("Too many attempts!")
        break
break_continue.py
Use break to exit early when you've found what you're looking for. Use continue to skip items that don't meet a criterion — it keeps the main loop body at a lower indentation level.

The else Clause on Loops

Python uniquely allows an else block on loops. It runs only if the loop completed normally (was NOT terminated by break):

# Search with else — clean "not found" handling
targets = [2, 4, 6, 8, 10]
search = 7

for n in targets:
    if n == search:
        print(f"Found {search}!")
        break
else:
    # This runs only if break was NEVER hit
    print(f"{search} not found in list.")
# Output: 7 not found in list.

# With while
n = 0
while n < 100:
    if is_prime(n) and n > 50:
        print(f"First prime > 50: {n}")
        break
    n += 1
else:
    print("No prime > 50 found under 100")
loop_else.py
Think of loop else as "no-break" — it executes when the loop finishes without hitting break. It's most useful for search patterns where you need to know if nothing was found.

Nested Loops

# Multiplication table
for i in range(1, 6):
    for j in range(1, 6):
        print(f"{i*j:3}", end=" ")
    print()  # newline after each row
#   1   2   3   4   5
#   2   4   6   8  10
#   3   6   9  12  15
#   ...

# Iterate over a 2D grid
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for row in matrix:
    for cell in row:
        print(cell, end=" ")
    print()

# Finding pairs
names = ["Alice", "Bob", "Charlie"]
for i, name1 in enumerate(names):
    for name2 in names[i+1:]:
        print(f"{name1} & {name2}")
nested_loops.py
break only exits the innermost loop. To break out of multiple nested loops, use a flag variable, a function with return, or restructure with itertools.product().

Common Loop Patterns

# Accumulator pattern
total = 0
for n in [10, 20, 30, 40]:
    total += n
print(total)  # 100

# Building a list
squares = []
for n in range(1, 6):
    squares.append(n ** 2)
print(squares)  # [1, 4, 9, 16, 25]
# Better: list comprehension → [n**2 for n in range(1, 6)]

# Filter pattern
words = ["hello", "world", "hi", "hey", "howdy"]
h_words = []
for w in words:
    if w.startswith("h"):
        h_words.append(w)
print(h_words)  # ['hello', 'hi', 'hey', 'howdy']

# zip — parallel iteration
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
for name, score in zip(names, scores):
    print(f"{name}: {score}")

# enumerate with start
for rank, name in enumerate(sorted(names), start=1):
    print(f"{rank}. {name}")
patterns.py

Common Pitfalls

# ❌ Modifying a list while iterating over it
items = [1, 2, 3, 4, 5]
# for item in items:
#     if item % 2 == 0:
#         items.remove(item)  # DANGEROUS — skips elements!

# ✓ Solution 1: iterate over a copy
for item in items[:]:       # slice creates a copy
    if item % 2 == 0:
        items.remove(item)

# ✓ Solution 2: build a new list (preferred)
items = [1, 2, 3, 4, 5]
items = [x for x in items if x % 2 != 0]  # [1, 3, 5]

# ❌ Forgetting to update the condition variable in while
# count = 0
# while count < 5:
#     print(count)
#     # forgot count += 1 → infinite loop!

# ❌ Off-by-one errors with range
# Want 1-10: range(1, 11) not range(1, 10)
for i in range(1, 11):    # 1 through 10 inclusive
    print(i)
pitfalls.py
🤖

Ask your AI tutor! Not sure how loop else works? Getting unexpected results from nested loops? Want to know when a list comprehension is better than a for loop? Ask — loops are where you build real programs.

💻 Exercises

01 FizzBuzz (Full)

Print numbers from 1 to 100. For multiples of 3 print "Fizz", for multiples of 5 print "Buzz", for multiples of both print "FizzBuzz", otherwise print the number.

Show solution
for n in range(1, 101):
    if n % 3 == 0 and n % 5 == 0:
        print("FizzBuzz")
    elif n % 3 == 0:
        print("Fizz")
    elif n % 5 == 0:
        print("Buzz")
    else:
        print(n)
02 Number Guessing Game

Write a number guessing game. Pick a secret number (e.g. 42). Use a while loop to repeatedly ask the user for a guess. Print "Too high" or "Too low" as hints. When they guess correctly, print how many attempts it took. Limit to 7 attempts.

Show solution
secret = 42
attempts = 0
max_attempts = 7

while attempts < max_attempts:
    guess = int(input("Guess (1-100): "))
    attempts += 1

    if guess == secret:
        print(f"Correct! It took {attempts} attempt(s).")
        break
    elif guess < secret:
        print("Too low!")
    else:
        print("Too high!")
else:
    print(f"Out of attempts! The number was {secret}.")
03 Prime Numbers

Write a script that finds all prime numbers between 2 and 50. A number is prime if it's only divisible by 1 and itself. Use a nested loop and break/else.

Show solution
primes = []

for n in range(2, 51):
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            break   # not prime
    else:
        # Loop completed without break → n is prime
        primes.append(n)

print(f"Primes up to 50: {primes}")
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]