🎯 Learning Objectives
- Use
forloops to iterate over sequences, ranges, and dictionaries - Use
whileloops for condition-based repetition - Control loop execution with
break,continue, andelse - 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
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.
| Call | Produces | Notes |
|---|---|---|
range(5) | 0, 1, 2, 3, 4 | Start defaults to 0; stop is exclusive |
range(2, 6) | 2, 3, 4, 5 | Start inclusive, stop exclusive |
range(0, 10, 2) | 0, 2, 4, 6, 8 | Step of 2 |
range(10, 0, -1) | 10, 9, 8, …, 1 | Count 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
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 iterate | You don't know how many iterations ahead of time |
| You want to loop a known number of times | You're waiting for a condition to change |
| Processing each item in a sequence | User input loops, polling, convergence algorithms |
break and continue
| Statement | Effect |
|---|---|
break | Exit the loop immediately (skip remaining iterations) |
continue | Skip 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
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
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
Primary sources: Python Docs — for Statements · Python Docs — range()
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
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)
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}.")
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]