Introduction to Loops
Imagine you need to print "Hello" 1000 times. Without loops, you would write 1000 print statements - tedious and error-prone. Loops are your automation superpower - they repeat code blocks efficiently, whether iterating through data, processing files, or running calculations until a condition is met.
What are Loops?
A loop is a programming instruction that tells the computer: "Keep doing this task until I tell you to stop." Think of it like setting your alarm to repeat every morning - you set it once, and it automatically goes off each day without you having to reset it.
Python gives you two types of loops:
- For loops - Use when you know exactly how many times to repeat (like "do this 10 times" or "do this for each item in my list")
- While loops - Use when you want to keep going until something changes (like "keep asking until the user types 'quit'")
Why it matters: Without loops, printing numbers 1 to 1000 would require 1000 separate print statements! Loops let you write the instruction once and repeat it automatically. They are essential for processing lists of data (like all students in a class), running games (the game loop keeps running until you quit), and automating any repetitive task.
Types of Loops in Python
Python offers two primary loop constructs, each suited for different scenarios. Understanding when to use each is key to writing clean, efficient code.
For Loop
What it does: Goes through each item in a collection one by one, like reading each page of a book from start to finish.
When to use:
- "Print each name in this list"
- "Repeat this 10 times"
- "Check each letter in this word"
Best when you know the exact count or have a collection to process.
While Loop
What it does: Keeps running as long as a condition is true, like eating chips until the bag is empty - you do not count how many, you just keep going until done.
When to use:
- "Keep asking until user enters valid input"
- "Run the game until player loses"
- "Download until file is complete"
Best when you do not know how many times you will need to repeat.
+---------------------------+
| Need to repeat code? |
+-------------+-------------+
|
+-------------v-------------+
| Know exact iterations? |
+-------------+-------------+
| |
YES NO
| |
+---------v------+ +-----v----------+
| FOR LOOP | | WHILE LOOP |
| for item in | | while condition|
| sequence: | | is True: |
+----------------+ +----------------+
For Loops
The for loop is Python's workhorse for iteration. It walks through each item in a sequence one by one - like going through a checklist, handling each item before moving to the next. When you have a list of students to grade, files to process, or numbers to calculate, the for loop handles them all automatically.
For Loop
A for loop automatically visits each item in a sequence (like a list, string, or range of numbers) and runs your code for each one. You do not need to manually track which item you are on - Python handles that for you.
The basic pattern:
for variable_name in sequence:
# do something with variable_name
How to read it: "For each item in this sequence, call it variable_name and run the indented code."
fruit or i) is created by the loop - you do not need to define it first. Pick a name that describes what each item represents!
fruits = ["apple", "banana", "cherry"]
Iteration 1: [apple] --> banana --> cherry fruit = "apple"
Iteration 2: apple --> [banana] --> cherry fruit = "banana"
Iteration 3: apple --> banana --> [cherry] fruit = "cherry"
Done! apple --> banana --> cherry Loop exits
+--------+ +--------+ +--------+
| apple | --> | banana | --> | cherry | --> END
+--------+ +--------+ +--------+
^ ^ ^
Loop 1 Loop 2 Loop 3
Basic For Loop Syntax
The for loop uses the in keyword to iterate over any iterable object - lists, strings, tuples, dictionaries, or ranges.
# Basic for loop - iterate through a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# Output:
# apple
# banana
# cherry
Step-by-step breakdown:
- Line 2: Define a list called
fruitswith three string elements - Line 3: Start the for loop -
fruitis the loop variable that takes each value - Line 4: The indented code block runs once for each item in the list
- Execution: First iteration: fruit="apple", second: fruit="banana", third: fruit="cherry"
The range() Function
The range() function is your number generator - it creates a sequence of numbers for your loop to use. Think of it as a counting machine: you tell it where to start, where to stop, and how to count (by 1s, 2s, 10s, or even backwards!).
range() stops before the number you give it. So range(5) gives you 0, 1, 2, 3, 4 - five numbers, but not 5 itself. This is like saying "count up to 5" rather than "count to and including 5."
# range(stop) - generates 0 to stop-1
for i in range(5):
print(i, end=" ") # Output: 0 1 2 3 4
# range(start, stop) - generates start to stop-1
for i in range(2, 6):
print(i, end=" ") # Output: 2 3 4 5
# range(start, stop, step) - with custom increment
for i in range(0, 10, 2):
print(i, end=" ") # Output: 0 2 4 6 8
range() patterns explained:
range(5)- Start at 0, go up to (but not including) 5range(2, 6)- Start at 2, go up to (but not including) 6range(0, 10, 2)- Start at 0, go up to 10, stepping by 2 (even numbers)range(10, 0, -1)- Count backwards from 10 to 1
Iterating Over Strings
Strings are sequences of characters, so you can loop through each character individually.
# Loop through each character in a string
word = "Python"
for char in word:
print(char, end="-")
# Output: P-y-t-h-o-n-
# With index using enumerate()
for index, char in enumerate(word):
print(f"Index {index}: {char}")
What is enumerate()? When you loop through a list, sometimes you need to know "which position am I at?" The enumerate() function solves this by giving you two things at once: the position number (index) and the actual item.
Without enumerate: You would need to create a counter variable, remember to increment it, and manage two separate things. Messy!
With enumerate: Just write for index, value in enumerate(sequence) and Python handles the counting for you. The index starts at 0 and goes up automatically.
Iterating Over Dictionaries
Dictionaries can be iterated by keys, values, or key-value pairs using built-in methods.
student = {"name": "Alice", "age": 20, "grade": "A"}
# Loop through keys (default)
for key in student:
print(key) # Output: name, age, grade
# Loop through values
for value in student.values():
print(value) # Output: Alice, 20, A
# Loop through key-value pairs
for key, value in student.items():
print(f"{key}: {value}")
Dictionary iteration methods:
for key in dict- Iterates over keys only (default behavior)for value in dict.values()- Iterates over values onlyfor key, value in dict.items()- Iterates over both as tuples
enumerate() when you need indices, zip() when iterating over multiple sequences in parallel, and .items() for dictionaries when you need both keys and values.
Looping with zip() - Parallel Iteration
The zip() function allows you to iterate over multiple sequences simultaneously, pairing elements by their position.
# Iterate over two lists in parallel
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Output: Alice: 85, Bob: 92, Charlie: 78
How zip() works: It creates an iterator of tuples where each tuple contains the i-th element from each of the input sequences. Iteration stops when the shortest sequence is exhausted.
# Combine three lists
first = ["Alice", "Bob"]
last = ["Smith", "Jones"]
ages = [25, 30]
for f, l, a in zip(first, last, ages):
print(f"{f} {l}, age {a}")
Step-by-step breakdown:
- Line 1-3: Define three separate lists - first names, last names, and ages
- Line 5:
zip()pairs elements by position: ("Alice", "Smith", 25), ("Bob", "Jones", 30) - Line 5: We "unpack" each tuple into three variables:
f,l,a - Line 6: Use f-string to format and print each person's full info
Why this is useful: When you have related data split across multiple lists (like database columns), zip() lets you process them together without managing index variables.
Here are some patterns you will use frequently in real-world Python programming.
# Build a new list with filtered items
numbers = [1, 2, 3, 4, 5, 6]
evens = []
for n in numbers:
if n % 2 == 0:
evens.append(n)
# evens = [2, 4, 6]
# Create new list with transformed items
words = ["hello", "world"]
upper = []
for w in words:
upper.append(w.upper())
# upper = ["HELLO", "WORLD"]
# Build up a single result
prices = [10.5, 20.0, 15.75]
total = 0
for price in prices:
total += price
# total = 46.25
# Find first match
users = ["alice", "bob", "admin"]
found = None
for user in users:
if user == "admin":
found = user
break
Understanding the Four Essential Loop Patterns:
- Filter Pattern: Create a new list containing only items that meet a condition. Start with an empty list, loop through the original, and
append()only the matching items. Example: extracting even numbers, finding users over age 18, getting files with a specific extension. - Transform Pattern: Create a new list where each item is modified from the original. Loop through the source and
append()the transformed version. Example: converting all strings to uppercase, doubling all numbers, extracting usernames from email addresses. - Accumulate Pattern: Build up a single result from all items. Start with an initial value (0 for sums, "" for strings, [] for lists), then update it with each item. Example: calculating totals, counting occurrences, building a combined string.
- Search Pattern: Find the first item matching a condition, then stop. Use a variable to store the result (initialized to
None) andbreakwhen found. Example: finding an admin user, locating an error in logs, finding the first available slot.
Practice: For Loop Exercises
Task: Write a for loop using range() to calculate the sum of all integers from 1 to 100. Print the final sum.
Show Solution
total = 0
for num in range(1, 101):
total += num
print(f"Sum of 1 to 100: {total}")
# Output: Sum of 1 to 100: 5050
Task: Write a program that counts the number of vowels (a, e, i, o, u) in the string "Programming is fun". Use a for loop to iterate through each character.
Show Solution
text = "Programming is fun"
vowels = "aeiouAEIOU"
count = 0
for char in text:
if char in vowels:
count += 1
print(f"Vowel count: {count}")
# Output: Vowel count: 5
Task: Given the list [34, 67, 23, 89, 12, 45, 78], find the largest number using a for loop (do not use the built-in max() function).
Show Solution
numbers = [34, 67, 23, 89, 12, 45, 78]
largest = numbers[0] # Assume first is largest
for num in numbers:
if num > largest:
largest = num
print(f"Largest: {largest}")
# Output: Largest: 89
Task: Write a program using nested for loops to print a multiplication table from 1 to 5. Format the output in a grid with proper alignment.
Show Solution
print(" ", end="")
for i in range(1, 6):
print(f"{i:4}", end="")
print("\n" + "-" * 22)
for i in range(1, 6):
print(f"{i} |", end="")
for j in range(1, 6):
print(f"{i*j:4}", end="")
print()
# Output: 5x5 multiplication grid
Task: Print numbers 1-20. For multiples of 3 print "Fizz", for multiples of 5 print "Buzz", for multiples of both print "FizzBuzz".
Show Solution
for i in range(1, 21):
if i % 3 == 0 and i % 5 == 0:
print("FizzBuzz")
elif i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
else:
print(i)
Task: Given a 3x3 matrix, transpose it (swap rows and columns) using nested loops.
Show Solution
matrix = [[1,2,3], [4,5,6], [7,8,9]]
transposed = []
for i in range(3):
row = []
for j in range(3):
row.append(matrix[j][i])
transposed.append(row)
# [[1,4,7], [2,5,8], [3,6,9]]
While Loops
While loops keep running as long as a condition stays True - like a security guard who keeps checking until the shift ends. They are perfect when you do not know in advance how many iterations you need.
count = 0
while count < 3:
print(count)
count += 1
+------------------+
| count = 0 |
+--------+---------+
|
v
+--------+---------+ NO
| count < 3 ? +-----------> EXIT LOOP
+--------+---------+
| YES
v
+------------------+
| print(count) |
| count += 1 |
+--------+---------+
|
+-----> (back to condition check)
Trace:
count=0: 0 < 3? YES -> print 0 -> count=1
count=1: 1 < 3? YES -> print 1 -> count=2
count=2: 2 < 3? YES -> print 2 -> count=3
count=3: 3 < 3? NO -> EXIT
While Loop
A while loop keeps running your code as long as a condition remains true. Before each repetition, Python checks the condition - if it is still true, the code runs again. If false, the loop stops and Python moves on.
The basic pattern:
while condition_is_true:
# do something
# (make sure something changes so the condition eventually becomes false!)
How to read it: "While this condition is true, keep doing the indented code."
Basic While Loop Syntax
The while loop evaluates its condition before each iteration. Before running the code block, Python asks: "Is this condition still True?" If yes, run the code. If no, skip the entire block and continue with the rest of the program.
# Basic while loop - count from 1 to 5
count = 1
while count <= 5:
print(count)
count += 1 # Important: update the condition variable!
# Output: 1 2 3 4 5
The Three Essential Parts of Every While Loop:
-
1. Initialization (before the loop): Create and set the variable you will check.
count = 1- We start counting at 1. -
2. Condition (in the while statement): The question Python asks before each run.
while count <= 5:- "Is count still 5 or less? If yes, run the code." -
3. Update (inside the loop): Change something so the condition can eventually become False.
count += 1- Add 1 to count each time. Eventually count becomes 6, which is not <= 5, so the loop stops.
Practical While Loop Examples
While loops shine when the number of iterations depends on user input, external data, or complex conditions.
# Example: Sum numbers until user enters 0
total = 0
num = int(input("Enter a number (0 to stop): "))
while num != 0:
total += num
num = int(input("Enter a number (0 to stop): "))
print(f"Total: {total}")
Why while loop here? We cannot know in advance how many numbers the user will enter. The loop continues until the user signals they are done by entering 0. This pattern is called a sentinel-controlled loop.
# Example: Find the first power of 2 greater than 1000
power = 1
exponent = 0
while power <= 1000:
exponent += 1
power = 2 ** exponent
print(f"2^{exponent} = {power}") # Output: 2^10 = 1024
Step-by-step breakdown:
- Line 2: Start with power = 1 (which is 2⁰)
- Line 3: Track the exponent separately
- Line 4: Keep looping while the power is still 1000 or less
- Line 5-6: Increase exponent by 1, then calculate 2 raised to that power
- Trace: 2¹=2, 2²=4, 2³=8... 2⁹=512 (≤1000, continue), 2¹⁰=1024 (>1000, stop!)
Why while loop here? We do not know in advance which exponent will give us a result > 1000. The loop keeps trying until it finds the answer - classic "search until condition is met" pattern.
count += 1 is forgotten, the loop runs forever. Use Ctrl+C to stop a runaway loop.
The Loop-Else Clause
Python has a unique feature: an else clause on loops that executes when the loop completes normally (without hitting a break).
# Loop-else: else runs if loop completes without break
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(f"{n} = {x} * {n//x}")
break
else: # No break occurred - n is prime
print(f"{n} is prime")
How loop-else works (this confuses many beginners!):
Normally, else means "otherwise." But in loops, else means "if the loop finished completely without being interrupted by break."
- If the loop hits
break: The else block is SKIPPED - If the loop runs to completion: The else block RUNS
Real-world analogy: Imagine searching your backpack for your keys. You check each pocket (the loop). If you find them, you stop searching (break) and do not say "I could not find them." But if you check every pocket without finding them, you say "I could not find them" (the else block).
Common While Loop Patterns
While loops are essential for certain patterns that cannot be elegantly expressed with for loops.
# Keep asking until valid input
age = -1
while age < 0 or age > 120:
age = int(input("Enter age: "))
if age < 0 or age > 120:
print("Invalid age!")
print(f"Age: {age}")
# Run until player quits
playing = True
while playing:
action = input("Action: ")
if action == "quit":
playing = False
else:
process(action)
# Retry with attempts limit
attempts = 0
success = False
while attempts < 3 and not success:
success = try_connection()
attempts += 1
# Tries up to 3 times
# Run until condition met
epsilon = 0.0001
x = 10.0
while abs(x - target) > epsilon:
x = (x + target/x) / 2
# Newton's method example
When to Use Each Pattern:
- Input Validation: Keep asking the user until they provide valid data. The loop ensures you never proceed with bad input - essential for robust programs that do not crash on user errors.
- Game Loop: The core of any interactive application. Runs continuously, processing user actions until they decide to quit. Games, chat programs, and menu systems all use this pattern.
- Retry Pattern: Handles temporary failures gracefully. Network connections, file operations, and API calls can fail temporarily - retrying a few times often succeeds. The
attempts < 3 and not successensures we give up eventually. - Convergence: Used in mathematical algorithms that refine a guess until it is "close enough." Machine learning, physics simulations, and numerical analysis often use this pattern. The
epsilon(ε) defines what "close enough" means.
Infinite Loops - When They Are Useful
While infinite loops are usually bugs, they are sometimes intentional - servers, event loops, and interactive programs often use while True with explicit breaks.
# Menu-driven program with intentional infinite loop
while True:
print("\n1. Add 2. View 3. Exit")
choice = input("Choice: ")
if choice == "1":
add_item()
elif choice == "2":
view_items()
elif choice == "3":
print("Goodbye!")
break # Exit the infinite loop
else:
print("Invalid choice")
Understanding the while True pattern:
- Line 2:
while Truecreates an intentional infinite loop - it would run forever without a break - Line 3-4: Display the menu and get user input on each iteration
- Lines 5-10: Check user's choice and take appropriate action
- Lines 8-9: When user selects "3", print goodbye and
breakexits the loop
Why use while True? It is cleaner than managing a running = True flag when:
- The exit condition is checked in the middle of the loop (not at the start)
- There are multiple places where the loop might exit
- The exit logic is clearer as a direct
breakstatement
while True to keep listening for requests. The loop only breaks when you manually stop the server (Ctrl+C) or the program receives a shutdown signal.
Practice: While Loop Exercises
Task: Write a while loop that counts down from 10 to 1 and prints "Liftoff!" at the end.
Show Solution
count = 10
while count >= 1:
print(count)
count -= 1
print("Liftoff!")
# Output: 10, 9, 8... 1, Liftoff!
Task: Write a while loop to calculate the factorial of 6 (6! = 6*5*4*3*2*1 = 720).
Show Solution
n = 6
factorial = 1
while n > 0:
factorial *= n
n -= 1
print(f"6! = {factorial}")
# Output: 6! = 720
Task: Write a while loop to reverse the digits of the number 12345. The result should be 54321.
Show Solution
num = 12345
reversed_num = 0
while num > 0:
digit = num % 10
reversed_num = reversed_num * 10 + digit
num //= 10
print(f"Reversed: {reversed_num}")
# Output: Reversed: 54321
Task: Write a while loop to find the GCD (Greatest Common Divisor) of 48 and 18 using the Euclidean algorithm: repeatedly replace the larger number with the remainder of dividing larger by smaller.
Show Solution
a, b = 48, 18
while b != 0:
a, b = b, a % b
print(f"GCD: {a}")
# Output: GCD: 6
Task: Start with n=13. While n is not 1: if n is even, divide by 2; if odd, multiply by 3 and add 1. Print each value in the sequence.
Show Solution
n = 13
while n != 1:
print(n, end=" -> ")
if n % 2 == 0:
n = n // 2
else:
n = 3 * n + 1
print(n)
# 13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1
Task: Convert the binary number 1101 to decimal using a while loop. Process each digit from right to left.
Show Solution
binary = 1101
decimal = 0
power = 0
while binary > 0:
digit = binary % 10
decimal += digit * (2 ** power)
binary //= 10
power += 1
print(f"Decimal: {decimal}")
# Output: Decimal: 13
Break and Continue
Sometimes you need more control over your loop. Maybe you found what you were looking for and want to stop early, or maybe you want to skip certain items without stopping entirely. That is where break and continue come in - think of them as your loop's "emergency exit" and "skip this one" buttons.
break
What it does: Immediately exits the loop completely. The loop is done - Python moves on to whatever code comes after the loop.
Analogy: You are searching your house for your keys. You check each room (loop). When you find them, you stop searching (break) - no need to check the remaining rooms!
Only exits the innermost loop if you have nested loops.
continue
What it does: Skips the rest of the current iteration and jumps to the next one. The loop keeps running - you just skip this particular item.
Analogy: You are grading papers. If you see a paper with no name, you skip it (continue) and move to the next paper. You do not stop grading entirely!
Everything after continue in the loop body is skipped for that iteration only.
BREAK - Exit the entire loop immediately
+-----------------------------------------+
| for i in [1, 2, 3, 4, 5]: |
| if i == 3: |
| break ---------> EXIT LOOP |
| print(i) |
+-----------------------------------------+
Output: 1, 2 (stops before printing 3)
CONTINUE - Skip to next iteration
+-----------------------------------------+
| for i in [1, 2, 3, 4, 5]: |
| if i == 3: |
| continue ----+ |
| print(i) | |
| ^-----------+ (skip to next) |
+-----------------------------------------+
Output: 1, 2, 4, 5 (skips 3, continues loop)
The break Statement
The break statement immediately terminates the innermost loop. As soon as Python hits break, it exits the loop and continues with whatever code comes next - the remaining items in the sequence are never processed.
# Find the first even number in a list
numbers = [1, 3, 5, 6, 7, 8, 9]
for num in numbers:
if num % 2 == 0:
print(f"First even: {num}") # Output: First even: 6
break
Step-by-step breakdown:
- Python checks 1 - not even (1 % 2 = 1), continues loop
- Python checks 3 - not even, continues loop
- Python checks 5 - not even, continues loop
- Python checks 6 - even! (6 % 2 = 0) - prints message and hits
break - Loop exits immediately - 7, 8, and 9 are never checked
When to use break:
- Searching: Stop when you find what you are looking for
- User input: Exit when user types "quit" or valid input received
- Error conditions: Stop processing if something goes wrong
- Efficiency: Why keep searching after you have found the answer?
The continue Statement
The continue statement skips the rest of the current iteration and moves to the next one. The loop keeps running - you just tell Python "I do not want to process this particular item, move on to the next."
# Print only odd numbers, skip even
for i in range(1, 11):
if i % 2 == 0:
continue # Skip even numbers
print(i, end=" ")
# Output: 1 3 5 7 9
Step-by-step breakdown:
- i = 1: Is 1 even? No. Continue to print. Output: 1
- i = 2: Is 2 even? Yes! Hit
continue, skip print, go to next - i = 3: Is 3 even? No. Continue to print. Output: 3
- i = 4: Is 4 even? Yes! Hit
continue, skip print, go to next - ...and so on for all 10 iterations
When to use continue:
- Filtering data: Skip items that do not meet your criteria
- Avoiding deep nesting: Instead of wrapping code in if-else, use continue to skip bad cases early
- Data cleaning: Skip invalid entries (blank lines, corrupted data, etc.)
continue at the start of a loop to skip invalid cases makes your code cleaner than using nested if statements!
# Practical example: Process valid scores only
scores = [85, -1, 92, 101, 78, 88, -5]
valid_scores = []
for score in scores:
if score < 0 or score > 100:
continue # Skip invalid scores
valid_scores.append(score)
print(valid_scores) # Output: [85, 92, 78, 88]
Step-by-step breakdown:
- Line 2: Our list has some invalid scores (negative numbers and > 100)
- Line 3: Create an empty list to collect only valid scores
- Line 5-6: Check if score is invalid (< 0 or > 100). If so,
continueskips to the next iteration - Line 7: This line only runs if we did NOT hit continue - meaning the score is valid
Result: -1, 101, and -5 are skipped. Only 85, 92, 78, and 88 end up in valid_scores.
if score >= 0 and score <= 100: valid_scores.append(score) but using continue keeps the main logic less indented and easier to read.
Practice: Break, Continue & Patterns
Task: Given the list [1, -2, 3, -4, 5, -6, 7], use continue to skip negative numbers and print only positive ones.
Show Solution
numbers = [1, -2, 3, -4, 5, -6, 7]
for num in numbers:
if num < 0:
continue
print(num, end=" ")
# Output: 1 3 5 7
Task: Using a for loop with range(1, 100), find and print the first number divisible by both 3 and 5, then use break to stop the loop.
Show Solution
for num in range(1, 100):
if num % 3 == 0 and num % 5 == 0:
print(f"First divisible by 3 and 5: {num}")
break
# Output: First divisible by 3 and 5: 15
Task: Use nested loops to print a diamond pattern of asterisks with n=5 rows in the top half.
Show Solution
n = 5
# Upper half
for i in range(1, n + 1):
print(" " * (n - i) + "*" * (2*i - 1))
# Lower half
for i in range(n - 1, 0, -1):
print(" " * (n - i) + "*" * (2*i - 1))
# Output: Diamond shape
Task: Write a program using nested loops to find and print all prime numbers from 2 to 50. Use the loop-else clause to identify primes.
Show Solution
print("Primes up to 50:", end=" ")
for num in range(2, 51):
for i in range(2, int(num**0.5) + 1):
if num % i == 0:
break
else: # No divisor found - prime
print(num, end=" ")
# Output: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
Nested Loops
A nested loop is simply a loop inside another loop. The inner loop runs completely for each iteration of the outer loop. This is how you work with grids, tables, combinations, and any data that has multiple dimensions.
Nested Loop
A nested loop is a loop placed inside another loop. The outer loop runs once, then the inner loop runs all its iterations. Then the outer loop runs again, and the inner loop starts over from the beginning. This repeats until the outer loop finishes.
Real-world examples:
- Clock: For each hour (outer loop 1-12), the minute hand goes around 60 times (inner loop 0-59). That is 12 × 60 = 720 total minutes.
- Spreadsheet: For each row (outer loop), you process each cell in that row (inner loop).
- Classroom seating: For each row of desks (outer), you have multiple seats in that row (inner).
for i in range(3): # Outer loop: 0, 1, 2
for j in range(2): # Inner loop: 0, 1
print(f"({i},{j})")
Execution trace:
+-------+-------+---------+
| Outer | Inner | Output |
+-------+-------+---------+
| i = 0 | j = 0 | (0, 0) |
| i = 0 | j = 1 | (0, 1) |
+-------+-------+---------+
| i = 1 | j = 0 | (1, 0) |
| i = 1 | j = 1 | (1, 1) |
+-------+-------+---------+
| i = 2 | j = 0 | (2, 0) |
| i = 2 | j = 1 | (2, 1) |
+-------+-------+---------+
Total iterations: 3 x 2 = 6
Pattern Printing with Nested Loops
Nested loops are commonly used to create patterns, work with 2D data structures, and generate combinations.
# Print a rectangle pattern
rows, cols = 3, 5
for i in range(rows):
for j in range(cols):
print("*", end=" ")
print() # New line after each row
# Output:
# * * * * *
# * * * * *
# * * * * *
Understanding the rectangle pattern:
Let us trace through this step by step (rows=3, cols=5):
- Outer loop i=0: We are on row 1. Inner loop runs 5 times (j=0,1,2,3,4), printing 5 asterisks. Then
print()makes a new line. - Outer loop i=1: We are on row 2. Inner loop runs 5 times again, printing 5 more asterisks. New line.
- Outer loop i=2: We are on row 3. Inner loop runs 5 times, printing 5 asterisks. New line.
The key insight: The outer loop controls which row you are on. The inner loop prints all the columns for that row. Together, they create a 2D grid!
# Print a right triangle pattern
n = 5
for i in range(1, n + 1):
for j in range(i):
print("*", end=" ")
print()
# Output:
# *
# * *
# * * *
# * * * *
# * * * * *
How the triangle pattern works:
- Outer loop:
igoes from 1 to 5, controlling which row we are printing - Inner loop:
range(i)runsitimes - so row 1 prints 1 star, row 2 prints 2 stars, etc. - Row 1: i=1, inner runs 1 time → *
- Row 2: i=2, inner runs 2 times → * *
- Row 3: i=3, inner runs 3 times → * * *
The trick: By making the inner loop count depend on the outer loop variable (range(i)), each row gets progressively more characters!
Working with 2D Lists (Matrices)
A 2D list is a list that contains other lists - like a spreadsheet with rows and columns, or a grid of pixels in an image. To access every element, you need two loops: one to go through each row, and one to go through each column in that row.
# Process a 2D matrix
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
for row in matrix:
for element in row:
print(element, end=" ")
print()
# Output: 1 2 3 / 4 5 6 / 7 8 9
How this works:
matrixcontains 3 lists: [1,2,3], [4,5,6], and [7,8,9]- The outer loop gives us each row (first [1,2,3], then [4,5,6], etc.)
- The inner loop goes through each number in that row
- After processing all numbers in a row,
print()makes a new line
Real-World Nested Loop Examples
Beyond patterns, nested loops solve practical problems in data processing, game development, and algorithms.
# Check if any student passed all subjects
students = {
"Alice": [85, 90, 78],
"Bob": [60, 55, 70],
"Charlie": [92, 88, 95]
}
for name, scores in students.items():
passed_all = True
for score in scores:
if score < 60:
passed_all = False
break
if passed_all:
print(f"{name} passed all subjects!")
Understanding the student grades checker:
- Outer loop: Goes through each student and their scores (Alice, then Bob, then Charlie)
- Inner loop: For each student, checks every score in their list
- Early exit: If any score is below 60, we set
passed_all = Falseandbreak- no need to check remaining scores - Flag pattern: We use a "flag" variable (
passed_all) to remember if something went wrong
This is a very common real-world pattern: check each item in a collection, and stop early if you find a problem.
# Find pairs that sum to target
numbers = [2, 7, 11, 15, 3, 6]
target = 9
for i in range(len(numbers)):
for j in range(i + 1, len(numbers)):
if numbers[i] + numbers[j] == target:
print(f"{numbers[i]} + {numbers[j]} = {target}")
Understanding the "find pairs" pattern:
We want to find all pairs of numbers that add up to 9. But we do not want to:
- Compare a number with itself (2 + 2 is not a pair of different numbers)
- Count the same pair twice (if we find 2 + 7, we do not also want 7 + 2)
The solution: Start the inner loop at i + 1 instead of 0. This way, we only compare each pair once and never compare a number with itself.
Visualization: If i=0 (pointing at 2), j starts at 1 and goes through 7, 11, 15, 3, 6. If i=1 (pointing at 7), j starts at 2 and goes through 11, 15, 3, 6. We never go backwards!
Common Pitfalls with Loops
Even experienced programmers make these mistakes. When your loop does something unexpected, check for these common problems first:
print() statements inside to see what values your variables have at each step. This "print debugging" helps you see exactly what is happening!
# Wrong: misses last element
for i in range(len(items) - 1):
print(items[i])
# Correct: includes all elements
for i in range(len(items)):
print(items[i])
# Wrong: skips elements!
for item in items:
if condition:
items.remove(item)
# Correct: iterate over copy
for item in items[:]:
if condition:
items.remove(item)
# Wrong: infinite loop!
i = 0
while i < 10:
print(i)
# Missing: i += 1
# Correct
i = 0
while i < 10:
print(i)
i += 1
# Wrong: print runs once
for i in range(3):
x = i * 2
print(x) # Only prints 4
# Correct: print in loop
for i in range(3):
x = i * 2
print(x) # 0, 2, 4
Performance Tips
Getting your loop to work is the first step. Making it run fast is the second. When you are processing thousands or millions of items, these optimizations can mean the difference between waiting seconds and waiting hours.
Loop Optimization
Loop optimization means making your loops run faster without changing what they accomplish. The key insight: any code inside a loop runs many times, so even small improvements add up.
The 80/20 rule: In most programs, 80% of the time is spent in 20% of the code - often inside loops. Optimizing loops gives you the biggest performance gains.
Move Invariant Calculations Outside
If a calculation gives the same result every time through the loop, do it once before the loop starts. This is called "hoisting" the calculation out of the loop.
# len() called 1000 times
for i in range(1000):
if i < len(data) / 2:
process(data[i])
# len() called once
half = len(data) / 2
for i in range(1000):
if i < half:
process(data[i])
What is an "invariant" calculation?
An invariant is something that stays the same throughout the loop. In the "inefficient" example, len(data) / 2 returns the same value every single time - but we are computing it 1000 times!
- Inefficient: Calculate
len(data) / 2on every iteration = 1000 calculations - Efficient: Calculate once before the loop, store in
half, reuse = 1 calculation
Rule of thumb: If a value does not change inside the loop, compute it once before the loop starts. This is called "hoisting" the calculation out of the loop.
Use Built-in Functions
Python's built-in functions like sum(), max(), and min() are implemented in C (a much faster language) and are highly optimized. Whenever you can use a built-in instead of writing your own loop, do it!
# Slow: Python loop
total = 0
for n in numbers:
total += n
maximum = numbers[0]
for n in numbers:
if n > maximum:
maximum = n
# Fast: C implementation
total = sum(numbers)
maximum = max(numbers)
# Also: min(), len(), any(), all()
Common built-in functions that replace loops:
| Instead of writing a loop to... | Use this built-in |
|---|---|
| Add up all numbers | sum(numbers) |
| Find the largest value | max(numbers) |
| Find the smallest value | min(numbers) |
| Count items in a list | len(numbers) |
| Check if any item is True | any(conditions) |
| Check if all items are True | all(conditions) |
| Sort a list | sorted(numbers) |
Bonus: Built-ins also make your code shorter and easier to read. total = sum(prices) is clearer than a 4-line accumulator loop!
List Comprehensions vs Loops
For simple transformations and filters, list comprehensions are faster, shorter, and more "Pythonic" (the preferred Python style). They are essentially a shorthand for creating lists from loops.
[expression for item in sequence] means "create a list by taking each item from the sequence and applying the expression to it."
# Traditional loop - 4 lines
squares = []
for x in range(10):
squares.append(x ** 2)
# List comprehension - 1 line, same result!
squares = [x ** 2 for x in range(10)]
# With condition - filter and transform in one line
evens = [x for x in range(20) if x % 2 == 0]
Reading list comprehensions (they look confusing at first!):
- Basic:
[x ** 2 for x in range(10)]→ "For each x in 0-9, compute x squared, and collect all results in a list" - With filter:
[x for x in range(20) if x % 2 == 0]→ "For each x in 0-19, but only if x is even, add x to the list"
The structure: [EXPRESSION for VARIABLE in SEQUENCE if CONDITION]
Why faster? List comprehensions avoid the overhead of repeated .append() calls and are optimized at the bytecode level. Use them for simple transformations; use regular loops for complex logic.
Avoid String Concatenation in Loops
Strings in Python are immutable - they cannot be changed after creation. So when you write result += word, Python actually creates a completely new string by copying everything. With 1000 words, this means copying: 1 word, then 2 words, then 3 words... up to 1000 words. That is a lot of wasted copying!
result = ""
for word in words:
result += word + " "
# Creates new string each time!
result = " ".join(words)
# Single operation
# Or use list and join
parts = []
for word in words:
parts.append(word)
result = " ".join(parts)
Why is string += slow in loops?
- Strings are immutable: When you write
result += word, Python cannot modifyresult- it must create a brand new string - Each += copies everything: With "hello" + "world", Python copies all of "hello", then adds "world". With 1000 words, the last += copies all 999 previous words!
- O(n²) complexity: Adding n words takes 1 + 2 + 3 + ... + n operations = n²/2. Double the words, quadruple the time!
The solution: Build a list of strings (appending to lists is fast!), then use " ".join(list) at the end to combine them all in one operation.
timeit module to measure actual performance before and after optimization.
Interactive: Range Explorer
Experiment with the range() function by adjusting the parameters below. See how start, stop, and step values affect the generated sequence in real-time.
range(0, 10, 1)
Interactive: Loop Visualizer
Watch how a for loop iterates through a list step by step. Click the buttons to control the execution and observe how the loop variable changes with each iteration.
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
for fruit in fruits:
print(fruit)
Key Takeaways
Here are the most important concepts to remember from this lesson. If you understand these points, you have a solid foundation in Python loops!
For Loops = "Do This for Each Item"
Use for item in sequence: when you have a collection to process (list, string, range) or know exactly how many times to repeat. Python handles the counting for you!
While Loops = "Keep Going Until..."
Use while condition: when you do not know how many times to repeat - like reading user input until they type "quit" or searching until you find something.
range() = Your Number Generator
range(start, stop, step) creates number sequences. Remember: it stops BEFORE the stop value! range(5) gives you 0,1,2,3,4 (five numbers, not including 5).
break = "Exit Now!"
Use break to exit the loop immediately when you have found what you are looking for or hit an error condition. Stops the loop entirely.
continue = "Skip This One"
Use continue to skip the current item and move to the next. Great for filtering - process only the items you care about, skip the rest.
Nested Loops = Loops × Loops
A loop inside a loop multiplies iterations. 10 × 10 = 100 operations. Use for grids, tables, and combinations. Watch out for performance with large numbers!