Loops (for, while)

Loops allow you to execute a block of code repeatedly. They are essential when you need to perform the same action multiple times or process collections of data. Python provides two main types of loops: for loops and while loops.

The for Loop

The for loop in Python is designed to iterate over a sequence (like a list, tuple, dictionary, set, or string) or other iterable objects. The general syntax is:

for variable in iterable:
    # Code to execute in each iteration

Let’s explore some common uses of for loops:

Iterating Over a List

fruits = ["apple", "banana", "cherry"]

for fruit in fruits:
    print(f"I like {fruit}s")

# Output:
# I like apples
# I like bananas
# I like cherrys

In this example, the loop variable fruit takes on each value in the fruits list, one at a time.

Iterating Over a String

Strings are sequences of characters, so you can iterate over them:

message = "Hello"

for character in message:
    print(character)

# Output:
# H
# e
# l
# l
# o

Using the range() Function

The range() function generates a sequence of numbers, which is perfect for for loops:

# range(stop) - generates numbers from 0 to stop-1
for i in range(5):
    print(i)
# Output: 0 1 2 3 4

# range(start, stop) - generates numbers from start to stop-1
for i in range(2, 6):
    print(i)
# Output: 2 3 4 5

# range(start, stop, step) - generates numbers from start to stop-1 with step
for i in range(1, 10, 2):
    print(i)
# Output: 1 3 5 7 9

The range() function is commonly used when you need to repeat an action a specific number of times or when you need the indices of a sequence.

Iterating With Index Using enumerate()

If you need both the value and its position (index) in the sequence, use the enumerate() function:

fruits = ["apple", "banana", "cherry"]

for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")

# Output:
# 0: apple
# 1: banana
# 2: cherry

Iterating Over Dictionaries

When iterating over a dictionary, the loop variable takes on the keys:

person = {
    "name": "John",
    "age": 30,
    "city": "New York"
}

# Iterating over keys (default)
for key in person:
    print(f"Key: {key}, Value: {person[key]}")

# Explicitly iterating over keys
for key in person.keys():
    print(f"Key: {key}")

# Iterating over values
for value in person.values():
    print(f"Value: {value}")

# Iterating over key-value pairs
for key, value in person.items():
    print(f"Key: {key}, Value: {value}")

Important: When iterating over a dictionary, the order of items was not guaranteed before Python 3.7. Since Python 3.7, dictionaries maintain insertion order.

The while Loop

The while loop executes a block of code as long as a specified condition is True. The general syntax is:

while condition:
    # Code to execute in each iteration

Basic while Loop

count = 0

while count < 5:
    print(f"Count: {count}")
    count += 1  # Increment count to avoid an infinite loop

# Output:
# Count: 0
# Count: 1
# Count: 2
# Count: 3
# Count: 4

Important: Always ensure that the condition in a while loop will eventually become False, or you’ll create an infinite loop that will run forever (or until you force the program to stop).

User Input Validation with while

The while loop is particularly useful for validating user input:

while True:
    response = input("Enter 'yes' or 'no': ").lower()
    
    if response == "yes" or response == "no":
        break  # Exit the loop if valid input
    else:
        print("Invalid input. Try again.")

print(f"You entered: {response}")

This loop continues asking for input until the user enters either “yes” or “no”.

while Loop with a Counter

You can use a counter variable to control how many times a loop executes:

attempts = 0
max_attempts = 3

while attempts < max_attempts:
    password = input("Enter your password: ")
    
    if password == "secret":
        print("Access granted!")
        break
    else:
        attempts += 1
        remaining = max_attempts - attempts
        if remaining > 0:
            print(f"Incorrect password. {remaining} attempts remaining.")
        else:
            print("Access denied. Too many incorrect attempts.")

This example limits the user to three password attempts.

Loop Control Statements

Python provides several statements to control the flow of loops:

The break Statement

The break statement exits the innermost loop prematurely:

for i in range(1, 10):
    if i == 5:
        break  # Exit the loop when i equals 5
    print(i)

# Output: 1 2 3 4

The continue Statement

The continue statement skips the rest of the current iteration and jumps to the next iteration:

for i in range(1, 10):
    if i % 2 == 0:
        continue  # Skip even numbers
    print(i)

# Output: 1 3 5 7 9

The else Clause in Loops

Surprisingly, Python allows an else clause with loops. The else block executes after the loop completes normally (i.e., not via a break statement):

# Find a number in a list
numbers = [1, 3, 5, 7, 9]
search_for = 5

for num in numbers:
    if num == search_for:
        print(f"Found {search_for}!")
        break
else:
    print(f"{search_for} is not in the list.")

# Output: Found 5!

# Try searching for a number not in the list
search_for = 6

for num in numbers:
    if num == search_for:
        print(f"Found {search_for}!")
        break
else:
    print(f"{search_for} is not in the list.")

# Output: 6 is not in the list.

The else clause is useful for implementing search algorithms where you need to know whether the loop completed without finding the item.

Nested Loops

You can place one loop inside another to create nested loops:

for i in range(1, 4):  # Outer loop
    for j in range(1, 4):  # Inner loop
        print(f"({i}, {j})", end=" ")
    print()  # New line after each row

# Output:
# (1, 1) (1, 2) (1, 3) 
# (2, 1) (2, 2) (2, 3) 
# (3, 1) (3, 2) (3, 3) 

Nested loops are particularly useful for working with multi-dimensional data structures:

# Print a 2D list in a grid format
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

for row in matrix:
    for element in row:
        print(element, end=" ")
    print()  # New line after each row

# Output:
# 1 2 3 
# 4 5 6 
# 7 8 9 

List Comprehensions

Python offers a concise way to create lists using a single line of code called a list comprehension:

# Traditional approach
squares = []
for i in range(1, 6):
    squares.append(i ** 2)
print(squares)  # [1, 4, 9, 16, 25]

# List comprehension approach
squares = [i ** 2 for i in range(1, 6)]
print(squares)  # [1, 4, 9, 16, 25]

You can also add conditions to list comprehensions:

# Only include squares of even numbers
even_squares = [i ** 2 for i in range(1, 11) if i % 2 == 0]
print(even_squares)  # [4, 16, 36, 64, 100]

List comprehensions can replace many common for-loop patterns and make your code more concise and readable.

Dictionary Comprehensions

Similar to list comprehensions, you can create dictionaries in a concise way:

# Create a dictionary of squares
squares_dict = {i: i ** 2 for i in range(1, 6)}
print(squares_dict)  # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# Create a dictionary with a condition
even_squares_dict = {i: i ** 2 for i in range(1, 11) if i % 2 == 0}
print(even_squares_dict)  # {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}

Practical Examples

Example 1: Sum of Numbers

Calculate the sum of all numbers from 1 to n using both for and while loops:

def sum_with_for(n):
    """Calculate the sum of numbers from 1 to n using a for loop."""
    total = 0
    for i in range(1, n + 1):
        total += i
    return total

def sum_with_while(n):
    """Calculate the sum of numbers from 1 to n using a while loop."""
    total = 0
    i = 1
    while i <= n:
        total += i
        i += 1
    return total

# Test both functions
n = 10
print(f"Sum of numbers from 1 to {n} (for loop): {sum_with_for(n)}")
print(f"Sum of numbers from 1 to {n} (while loop): {sum_with_while(n)}")

Example 2: FizzBuzz

The classic programming challenge:

def fizzbuzz(n):
    """
    Print numbers from 1 to n, but for multiples of 3 print "Fizz",
    for multiples of 5 print "Buzz", and for multiples of both print "FizzBuzz".
    """
    for i in range(1, n + 1):
        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)

# Run FizzBuzz for numbers 1 to 15
fizzbuzz(15)

Example 3: Prime Number Finder

Find all prime numbers up to a given limit:

def find_primes(limit):
    """Find all prime numbers up to the given limit."""
    primes = []
    
    for num in range(2, limit + 1):
        is_prime = True
        
        # Check if num is divisible by any smaller number
        for divisor in range(2, int(num ** 0.5) + 1):
            if num % divisor == 0:
                is_prime = False
                break
        
        if is_prime:
            primes.append(num)
    
    return primes

# Find all primes up to 50
print(find_primes(50))

Example 4: Word Counter

Count the occurrences of each word in a text:

def count_words(text):
    """Count the occurrences of each word in the given text."""
    # Create an empty dictionary to store word counts
    word_counts = {}
    
    # Split the text into words
    words = text.split()
    
    # Loop through each word
    for word in words:
        # Remove punctuation and convert to lowercase
        clean_word = word.strip(".,!?\"'()[]{}:;").lower()
        
        if clean_word:  # Skip empty strings
            # Increment the count for this word
            if clean_word in word_counts:
                word_counts[clean_word] += 1
            else:
                word_counts[clean_word] = 1
    
    return word_counts

# Sample text
sample_text = """
Python is a powerful programming language. Python is easy to learn.
Python is used for web development, data analysis, artificial intelligence, and more.
"""

# Count words
result = count_words(sample_text)

# Print the results in a readable format
print("Word Counts:")
for word, count in sorted(result.items()):
    print(f"{word}: {count}")

Loop Efficiency and Best Practices

1. Choose the Right Loop

  • Use for loops when you know the number of iterations in advance or when iterating over a collection.
  • Use while loops when you need to continue until a condition changes or for indefinite iterations.
# Good use of for loop
for i in range(10):
    print(i)

# Good use of while loop
response = ""
while response != "quit":
    response = input("Enter a command (type 'quit' to exit): ")
    # Process the response

2. Avoid Modifying the Iteration Variable in a for Loop

In Python, the iteration variable is automatically updated by the loop mechanism. Modifying it manually can lead to unexpected behavior:

# Problematic - modifying the iteration variable
for i in range(5):
    print(i)
    i += 2  # This has no effect on the loop's behavior

# Better approach if you need to skip items
i = 0
while i < 5:
    print(i)
    i += 3  # Skip by incrementing more than 1

3. Avoid Modifying Collections During Iteration

Modifying a collection while iterating over it can lead to unexpected behavior:

# Problematic - modifying a list during iteration
numbers = [1, 2, 3, 4, 5]
for num in numbers:
    if num % 2 == 0:
        numbers.remove(num)  # This modifies the list being iterated

# Better approach
numbers = [1, 2, 3, 4, 5]
numbers = [num for num in numbers if num % 2 != 0]
# or
odd_numbers = []
for num in numbers:
    if num % 2 != 0:
        odd_numbers.append(num)
numbers = odd_numbers

4. Use enumerate() Instead of Manual Counting

# Manual index tracking
fruits = ["apple", "banana", "cherry"]
index = 0
for fruit in fruits:
    print(f"{index}: {fruit}")
    index += 1

# Better approach with enumerate()
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")

5. Use List Comprehensions for Simple Transformations

# Traditional loop for transformation
numbers = [1, 2, 3, 4, 5]
squared = []
for num in numbers:
    squared.append(num ** 2)

# Cleaner approach with list comprehension
numbers = [1, 2, 3, 4, 5]
squared = [num ** 2 for num in numbers]

6. Break Out of Complex Loops Early

def find_item(matrix, target):
    """Find an item in a 2D matrix."""
    for i, row in enumerate(matrix):
        for j, value in enumerate(row):
            if value == target:
                return (i, j)  # Return early when found
    return None  # Not found

7. Be Aware of the Performance Impact of Nested Loops

Nested loops multiply the number of iterations, which can impact performance for large data sets:

# This runs in O(n²) time
n = 1000
for i in range(n):
    for j in range(n):
        # Some operation

# Consider if there's a more efficient algorithm

Common Loop Patterns

Pattern 1: Loop with a Counter

count = 0
for item in collection:
    if condition(item):
        count += 1
print(f"Found {count} items matching the condition")

Pattern 2: Find the Maximum/Minimum Value

numbers = [23, 54, 12, 87, 34]
max_value = numbers[0]  # Assume the first item is the maximum

for num in numbers[1:]:  # Start from the second item
    if num > max_value:
        max_value = num

print(f"The maximum value is {max_value}")

Pattern 3: Filtering Items

original = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filtered = []

for num in original:
    if num % 2 == 0:  # Keep only even numbers
        filtered.append(num)

print(filtered)  # [2, 4, 6, 8, 10]

# Alternatively, using list comprehension
filtered = [num for num in original if num % 2 == 0]

Pattern 4: Aggregating Values

expenses = [120.50, 35.75, 240.00, 55.25, 100.00]
total = 0

for expense in expenses:
    total += expense

print(f"Total expenses: ${total:.2f}")

# Alternatively, using sum()
total = sum(expenses)

Pattern 5: Nested Loop for Combinations

fruits = ["apple", "banana", "cherry"]
colors = ["red", "green", "blue"]

combinations = []
for fruit in fruits:
    for color in colors:
        combinations.append((fruit, color))

print(combinations)
# [('apple', 'red'), ('apple', 'green'), ('apple', 'blue'),
#  ('banana', 'red'), ('banana', 'green'), ('banana', 'blue'),
#  ('cherry', 'red'), ('cherry', 'green'), ('cherry', 'blue')]

# Alternatively, using list comprehension
combinations = [(fruit, color) for fruit in fruits for color in colors]

Exercises

Exercise 1: Write a program that prints the multiplication table for a given number. For example, if the number is 5, it should print:

5 x 1 = 5
5 x 2 = 10
...
5 x 10 = 50

Exercise 2: Create a program that generates a list of all even numbers between 1 and 100 using a loop. Then create the same list using a list comprehension.

Exercise 3: Write a program that asks the user for a number and then prints all the factors of that number. A factor is a number that divides the given number without a remainder.

Exercise 4: Create a nested loop to generate a “pyramid” pattern like this:

*
**
***
****
*****

Hint for Exercise 1: Use a for loop with the range function to iterate from 1 to 10, and in each iteration, multiply the given number by the current loop variable.

# Exercise 1 solution outline
num = 5
for i in range(1, 11):
    result = num * i
    print(f"{num} x {i} = {result}")

In the next section, we’ll explore break and continue statements in more detail, learn about the pass statement, and introduce exception handling basics.