Module 3.2

Python Loops

Loops let your code do the boring repetitive work for you! Instead of writing the same line 100 times, loops execute code blocks repeatedly - like a tireless assistant that never complains about doing things over and over.

50 min
Beginner
Hands-on
What You'll Learn
  • For loops and the range() function
  • While loops and condition-based iteration
  • Break and continue statements
  • Nested loops for complex patterns
  • Loop-else clauses
Contents
01

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.

Key Concept

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.

Real-world analogy: A loop is like a washing machine cycle - it keeps agitating, rinsing, and spinning until the timer says stop. You press start once, and it handles all the repetition automatically.

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.

Loop Decision Flowchart
                    +---------------------------+
                    |   Need to repeat code?    |
                    +-------------+-------------+
                                  |
                    +-------------v-------------+
                    | Know exact iterations?    |
                    +-------------+-------------+
                          |               |
                         YES              NO
                          |               |
                +---------v------+  +-----v----------+
                |   FOR LOOP     |  |   WHILE LOOP   |
                | for item in    |  | while condition|
                |   sequence:    |  |   is True:     |
                +----------------+  +----------------+
02

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.

Definition

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

Beginner tip: The variable name (like 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!
How For Loops Iterate Through Sequences
    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:

  1. Line 2: Define a list called fruits with three string elements
  2. Line 3: Start the for loop - fruit is the loop variable that takes each value
  3. Line 4: The indented code block runs once for each item in the list
  4. 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!).

Important: 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) 5
  • range(2, 6) - Start at 2, go up to (but not including) 6
  • range(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.

Real example: If you want to print "1. Apple, 2. Banana, 3. Cherry" from a list, enumerate() is perfect - it gives you both the number and the fruit name!

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 only
  • for key, value in dict.items() - Iterates over both as tuples
Pro Tip: Use 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:

  1. Line 1-3: Define three separate lists - first names, last names, and ages
  2. Line 5: zip() pairs elements by position: ("Alice", "Smith", 25), ("Bob", "Jones", 30)
  3. Line 5: We "unpack" each tuple into three variables: f, l, a
  4. 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.

Filter Pattern
# 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]
Transform Pattern
# Create new list with transformed items
words = ["hello", "world"]
upper = []
for w in words:
    upper.append(w.upper())
# upper = ["HELLO", "WORLD"]
Accumulate Pattern
# Build up a single result
prices = [10.5, 20.0, 15.75]
total = 0
for price in prices:
    total += price
# total = 46.25
Search Pattern
# 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) and break when found. Example: finding an admin user, locating an error in logs, finding the first available slot.
Pro tip: Most real-world loops are variations of these four patterns. When starting a new loop, ask yourself: "Am I filtering, transforming, accumulating, or searching?" This helps you structure your code correctly from the start.

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]]
03

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.

While Loop Condition Checking Flow
    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
Definition

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

Critical warning: If you forget to change something that affects the condition, your loop will run forever (an "infinite loop"). Always make sure your loop can eventually stop!

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. 1. Initialization (before the loop): Create and set the variable you will check.
    count = 1 - We start counting at 1.
  2. 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. 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.
Common beginner mistake: Forgetting step 3 (the update) creates an infinite loop - the condition never becomes False, so the loop runs forever! If your program freezes, press Ctrl+C to stop it.

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:

  1. Line 2: Start with power = 1 (which is 2⁰)
  2. Line 3: Track the exponent separately
  3. Line 4: Keep looping while the power is still 1000 or less
  4. Line 5-6: Increase exponent by 1, then calculate 2 raised to that power
  5. 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.

Infinite Loop Warning: Always ensure your loop condition will eventually become False. If 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).

When to use it: Loop-else is perfect for search operations where you need to handle the "not found" case differently from the "found" case.

Common While Loop Patterns

While loops are essential for certain patterns that cannot be elegantly expressed with for loops.

Input Validation
# 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}")
Game Loop
# Run until player quits
playing = True
while playing:
    action = input("Action: ")
    if action == "quit":
        playing = False
    else:
        process(action)
Retry Pattern
# Retry with attempts limit
attempts = 0
success = False
while attempts < 3 and not success:
    success = try_connection()
    attempts += 1
# Tries up to 3 times
Convergence
# 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 success ensures 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:

  1. Line 2: while True creates an intentional infinite loop - it would run forever without a break
  2. Line 3-4: Display the menu and get user input on each iteration
  3. Lines 5-10: Check user's choice and take appropriate action
  4. Lines 8-9: When user selects "3", print goodbye and break exits 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 break statement
Real-world use: Web servers use 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
04

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.

Keyword

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.

Keyword

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 vs Continue Flow Control
    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:

  1. Python checks 1 - not even (1 % 2 = 1), continues loop
  2. Python checks 3 - not even, continues loop
  3. Python checks 5 - not even, continues loop
  4. Python checks 6 - even! (6 % 2 = 0) - prints message and hits break
  5. 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:

  1. i = 1: Is 1 even? No. Continue to print. Output: 1
  2. i = 2: Is 2 even? Yes! Hit continue, skip print, go to next
  3. i = 3: Is 3 even? No. Continue to print. Output: 3
  4. i = 4: Is 4 even? Yes! Hit continue, skip print, go to next
  5. ...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.)
Pro tip: Using 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:

  1. Line 2: Our list has some invalid scores (negative numbers and > 100)
  2. Line 3: Create an empty list to collect only valid scores
  3. Line 5-6: Check if score is invalid (< 0 or > 100). If so, continue skips to the next iteration
  4. 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.

Alternative approach: You could also write 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
05

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.

Definition

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).
Performance note: Nested loops multiply! If your outer loop runs 100 times and your inner loop runs 100 times, that is 10,000 total operations. Three nested loops of 100 each = 1,000,000 operations. Be careful with deeply nested loops!
Nested Loop Execution Pattern
    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):

  1. 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.
  2. Outer loop i=1: We are on row 2. Inner loop runs 5 times again, printing 5 more asterisks. New line.
  3. 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!

Tip: When working with nested loops, always think: "What does the outer loop represent? What does the inner loop represent?" This mental model helps you design the correct structure.
# 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:

  1. Outer loop: i goes from 1 to 5, controlling which row we are printing
  2. Inner loop: range(i) runs i times - so row 1 prints 1 star, row 2 prints 2 stars, etc.
  3. Row 1: i=1, inner runs 1 time → *
  4. Row 2: i=2, inner runs 2 times → * *
  5. 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:

  • matrix contains 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
Performance Note: Nested loops multiply iterations. A loop of 1000 inside a loop of 1000 equals 1,000,000 iterations. Be mindful of nested loop depth for large datasets.

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:

  1. Outer loop: Goes through each student and their scores (Alice, then Bob, then Charlie)
  2. Inner loop: For each student, checks every score in their list
  3. Early exit: If any score is below 60, we set passed_all = False and break - no need to check remaining scores
  4. 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:

Debugging tip: When a loop misbehaves, add print() statements inside to see what values your variables have at each step. This "print debugging" helps you see exactly what is happening!
Off-By-One Errors
# 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])
Modifying List While Iterating
# 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)
Forgetting to Update Counter
# Wrong: infinite loop!
i = 0
while i < 10:
    print(i)
    # Missing: i += 1

# Correct
i = 0
while i < 10:
    print(i)
    i += 1
Wrong Indentation
# 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
06

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.

Concept

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.

Beginner tip: Do not optimize prematurely! First make your code work correctly, then optimize if needed. A fast program that gives wrong answers is useless.

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.

Inefficient
# len() called 1000 times
for i in range(1000):
    if i < len(data) / 2:
        process(data[i])
Efficient
# 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) / 2 on 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!

Why built-ins are faster: Python is an "interpreted" language, meaning each line is translated while running. Built-in functions are pre-compiled in C, so they skip this translation step and run directly on your CPU. The difference can be 10-100x faster!
Manual Loop
# Slow: Python loop
total = 0
for n in numbers:
    total += n

maximum = numbers[0]
for n in numbers:
    if n > maximum:
        maximum = n
Built-in Functions
# 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 numberssum(numbers)
Find the largest valuemax(numbers)
Find the smallest valuemin(numbers)
Count items in a listlen(numbers)
Check if any item is Trueany(conditions)
Check if all items are Trueall(conditions)
Sort a listsorted(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.

Reading a list comprehension: [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!

Why it is slow: String concatenation in loops has O(n²) complexity - if you have 10x more items, it takes 100x longer, not 10x longer!
Slow - O(n^2) time
result = ""
for word in words:
    result += word + " "
# Creates new string each time!
Fast - O(n) 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?

  1. Strings are immutable: When you write result += word, Python cannot modify result - it must create a brand new string
  2. Each += copies everything: With "hello" + "world", Python copies all of "hello", then adds "world". With 1000 words, the last += copies all 999 previous words!
  3. 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.

Golden Rule: Write correct code first, then optimize only if needed. Use Python's 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)
Count: 10 Counts from 0 up to (not including) 10

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)
apple
banana
cherry
date
elderberry
Current Index: -
fruit = -
Iteration: 0 / 5
Output

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!

Knowledge Check

1 Which loop type is best when you do not know how many iterations are needed in advance?
2 What does range(2, 10, 2) generate?
3 What is the difference between break and continue?
4 How many times will the inner loop execute in total: for i in range(3): for j in range(4): print(i, j)
5 When does the else clause of a loop execute?
6 What function would you use to get both index and value while iterating through a list?
Answer all questions to check your score