Previous | Tutorial index | Next

Tutorial 5: Common Loop Patterns and Best Practices

Learning Objective

Move beyond basic syntax and learn the recurring "design patterns" of loops. You will learn how to solve common problems elegantly, use Python's built-in tools for cleaner code, and adopt professional best practices to write maintainable, bug-free loops.

1. The Philosophy of Loop Patterns

When you write code, you'll notice that certain problems appear over and over again. Instead of reinventing the wheel each time, you can recognize these patterns and apply a standardized solution.

Think of a chef in a kitchen. They don't invent a new way to chop an onion every timeβ€”they have a pattern (the knife technique) that they apply whenever they see an onion. Similarly, programmers have patterns for:

Mastering these patterns makes you a faster, more reliable programmer.

2. Pattern 1: The Accumulation Pattern

The accumulation pattern involves starting with a "blank slate" variable and gradually adding to it during each loop iteration.

A. Summing Numbers

total = 0 # The accumulator for i in range(1, 11): total = total + i # Or total += i print(total) # Output: 55

B. Calculating a Product

(Note: Product accumulators always start at 1, not 0, because multiplying by 0 ruins everything!)

factorial = 1 for i in range(1, 6): # 5! = 1*2*3*4*5 = 120 factorial *= i print(factorial) # Output: 120

C. Counting (Conditional Accumulation)

How many vowels are in a string?

text = "Hello World" vowel_count = 0 for char in text: if char.lower() in "aeiou": vowel_count += 1 print(vowel_count) # Output: 3 (e, o, o)

D. String Building (Concatenation)

words = ["Python", "is", "powerful"] sentence = "" for word in words: sentence += word + " " print(sentence.strip()) # Output: "Python is powerful"

⚠️ Pro-Tip: While += works for small strings, it is inefficient for large ones (it creates new strings repeatedly). For large-scale string building, use " ".join(words) or collect items in a list and join at the end.

3. Pattern 2: The Searching Pattern

Searching involves looking through a collection for the first item that meets a specific condition. Once found, you usually want to stop immediately (using break).

# Find the first number greater than 50 numbers = [10, 30, 60, 80, 20] target = None # Sentinel value to indicate "not found" for num in numbers: if num > 50: target = num break # Exit early! We found it. if target is not None: print(f"Found: {target}") else: print("No number greater than 50.")

The Search with Index: Sometimes you need to know where in the list the item is.

fruits = ["apple", "banana", "cherry", "durian"] search_item = "cherry" found_index = -1 # -1 is a common sentinel for "not found" for i in range(len(fruits)): if fruits[i] == search_item: found_index = i break if found_index != -1: print(f"Found '{search_item}' at index {found_index}") else: print("Not found.")

4. Pattern 3: The Flag Pattern

The flag pattern uses a Boolean variable (True / False) to track whether a specific condition occurred at any point during the loop. This is very common for data validation.

Example: Are there any negative numbers in this list?

numbers = [5, 12, -3, 8, 0] has_negative = False # The flag starts as False for num in numbers: if num < 0: has_negative = True break # We know the answer now, so we can stop! if has_negative: print("Warning: Negative number found!") else: print("All numbers are non-negative.")

Example: Checking if a number is Prime (using a flag).

n = 29 is_prime = True # Assume it's prime until proven otherwise if n < 2: is_prime = False else: for i in range(2, int(n**0.5) + 1): if n % i == 0: is_prime = False break print(f"{n} is prime: {is_prime}")

5. Pattern 4: Filtering Pattern

Filtering involves iterating over a collection and building a new collection containing only the elements that pass a certain test.

# Keep only the even numbers original = [1, 2, 3, 4, 5, 6, 7, 8] evens = [] for num in original: if num % 2 == 0: evens.append(num) print(evens) # Output: [2, 4, 6, 8]

(Note: In professional Python, we use List Comprehensions [num for num in original if num % 2 == 0], but the loop pattern is the foundation.)

6. Pythonic Tools: enumerate() and zip()

Python provides built-in functions that make loops cleaner, more readable, and more powerful. These are considered "Pythonic" (idiomatic to the language).

A. The enumerate() Function

If you need the index and the value simultaneously while looping, enumerate() is your best friend. It returns a tuple (index, value).

colors = ["red", "green", "blue"] # Without enumerate (clunky) for i in range(len(colors)): print(f"{i}: {colors[i]}") # With enumerate (clean and Pythonic) for index, color in enumerate(colors): print(f"{index}: {color}")

Bonus: You can start counting from a different number.

for index, color in enumerate(colors, start=1): print(f"Color #{index}: {color}")

Output: Color #1: red, Color #2: green, etc.

B. The zip() Function

If you need to loop over two or more lists in parallel, use zip(). It pairs up the elements at the same positions.

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

Output:

Alice scored 85% Bob scored 92% Charlie scored 78%

How zip() stops: It stops when the shortest input list ends.

a = [1, 2, 3] b = [10, 20] for x, y in zip(a, b): print(x, y) # Output: (1,10) and (2,20) -- 3 is ignored!

Advanced Use: Zipping three or more lists.

first = ["A", "B"] second = ["C", "D"] third = ["E", "F"] for x, y, z in zip(first, second, third): print(x, y, z) # Output: A C E, B D F

7. Best Practices and Critical Warnings

Practice 1: Keep Loop Bodies Small

A loop body should do one thing well. If your loop is 20 lines long, it's time to refactor. Move complex logic into a function outside the loop.

Bad:

for item in big_list: # 20 lines of complex validation, calculations, and file writing here. pass

Good:

def process_item(item): # All the complex logic lives here. return result for item in big_list: result = process_item(item) # Just one line in the loop.

Practice 2: Write Clear, Self-Documenting Conditions

Don't write cryptic conditions. Use meaningful variable names. Bad: while x > 0 and not y == 3 and z < 10: Good:

while remaining_items > 0 and not is_error_state and current_depth < max_depth:

Practice 3: 🚨 NEVER Modify a List While Iterating Over It! 🚨

This is the classic "unexpected behavior" trap. If you remove or add items to a list while a for loop is iterating over it, the internal index counter gets confused, and you will skip items or get IndexError.

❌ DANGEROUS CODE (Skipping items):

numbers = [1, 2, 3, 4, 5] for num in numbers: if num % 2 == 1: # If odd numbers.remove(num) # Remove it print(numbers) # Output: [2, 4] -- Wait, where is 3 and 5? They got skipped!

(Why? When you remove 1, the list shifts. The loop moves to index 1, which is now 3 (old index 2), so 2 is skipped!)

βœ… SAFE FIX #1: Iterate over a COPY.

numbers = [1, 2, 3, 4, 5] for num in numbers[:]: # The [:] creates a copy of the list. if num % 2 == 1: numbers.remove(num) print(numbers) # Output: [2, 4]

βœ… SAFE FIX #2: Build a New List (Filtering).

numbers = [1, 2, 3, 4, 5] evens = [] for num in numbers: if num % 2 == 0: evens.append(num) numbers = evens print(numbers) # Output: [2, 4]

Practice 4: Use else with Loops for Search Pattern

We covered this in Tutorial 3, but it bears repeating. The else clause runs only if the loop completes without a break. It is perfect for search patterns.

for num in [10, 20, 30]: if num == 15: print("Found!") break else: print("15 was not found.")

πŸ“ Quiz: Check Your Understanding

Q1: What is the value of total after this code runs?

total = 1 for i in range(1, 5): total *= i print(total)

a) 10
b) 24
c) 0

Answer(B) 24

Q2: What does the following code print?

names = ["Anna", "Bob", "Charlie"] for i, name in enumerate(names, start=10): print(i, end=" ")

a) 0 1 2
b) 10 11 12
c) Anna Bob Charlie

Answer(B)

Q3: What is the output of this code?

list1 = [1, 2, 3] list2 = ['a', 'b'] for x, y in zip(list1, list2): print(x, y)

a) (1,a), (2,b), (3, None)
b) (1,a), (2,b)
c) Error because lists have different lengths.

Answer(B)

Q4: (True/False) It is perfectly safe to delete items from a list while iterating over it using for item in my_list:.

AnswerFalse

Q5: Which pattern uses a boolean variable that starts as False and is set to True if something happens? a) Accumulation
b) Flag
c) Zip

Answer(B)

πŸ’» In-Tutorial Coding Exercises

Exercise 1 (Accumulation - Average): Write a program that asks the user to enter 5 numbers (use a for loop with range(5)). Accumulate their sum. After the loop, print the average (sum / 5).

Solution
total = 0 for _ in range(5): total += float(input("Enter number: ")) print(total/5)

Exercise 2 (Searching with enumerate): Given grades = [45, 67, 88, 52, 94], write a program that finds the first failing grade (below 60) and prints its index and value. Use enumerate() and break.

Solution
grades = [45,67,88,52,94] for idx, g in enumerate(grades): if g < 60: print(f"First failing: {g} at index {idx}") break

Exercise 3 (Flag - All Positive): Write a program that asks the user to enter 3 numbers. Use a flag to check if all numbers are positive. If any number is zero or negative, print "Not all positive". Otherwise, print "All positive!".

Solution
all_pos = True for _ in range(3): if float(input()) <= 0: all_pos = False print("All positive" if all_pos else "Not all positive")

Exercise 4 (Zip - Matches Counter): Given two lists: correct_answers = ["A", "B", "C", "D"] and student_answers = ["A", "B", "D", "D"]. Use zip() to count how many answers the student got correct.

Solution
correct = ["A","B","C","D"] student = ["A","B","D","D"] score = sum(1 for c,s in zip(correct, student) if c==s) print(f"Score: {score}/4")

Exercise 5 (Filtering with Copy): Given nums = [10, 21, 30, 43, 50, 61], write a loop that removes all odd numbers using a copy of the list (nums[:]). details>

Solution

nums = [10,21,30,43,50,61] for n in nums[:]: if n % 2 == 1: nums.remove(n) print(nums)

πŸ“š Homework Questions

Question 1: The GPA Calculator (Accumulation + Zip) You are given two lists: credits = [3, 4, 3, 2] and grade_points = [4.0, 3.0, 3.7, 2.0] (where A=4.0, B=3.0, etc.).

Sample Answer
credits = [3,4,3,2] grade_points = [4.0,3.0,3.7,2.0] total_points = 0 total_credits = 0 for c, g in zip(credits, grade_points): total_points += c * g total_credits += c print(f"GPA: {total_points/total_credits:.2f}")

Question 2: The Duplicate Detector (Flag Pattern) Write a program that takes a list (e.g., [1, 2, 3, 4, 2, 5]) and uses nested loops (a flag pattern) to check if there are any duplicate numbers. The flag should be has_duplicate. Print "Duplicates found" or "No duplicates" accordingly. (Hint: Compare list[i] with list[j] where i and j are different indices. Use break to optimize).

Sample Answer
lst = [1,2,3,4,2,5] has_dup = False for i in range(len(lst)): for j in range(i+1, len(lst)): if lst[i] == lst[j]: has_dup = True break if has_dup: break print("Duplicates found" if has_dup else "No duplicates")

Question 3: The Student Roster (Enumerate + Filtering) You have a list of student names: students = ["Alice", "Bob", "Charlie", "David", "Eve"]. You need to create a new list selected containing students whose index is even (0, 2, 4). Use enumerate() to check the index, and if the index is even, add the student to the new list. Print the selected list.

Sample Answer
students = ["Alice","Bob","Charlie","David","Eve"] selected = [name for idx, name in enumerate(students) if idx % 2 == 0] print(selected)

Question 4: The Matrix Column Sum (Nested Loops + Accumulation) Given a 2D matrix: matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]. Write a program that calculates the sum of each column (not row). Expected Output: Column 0 sum: 12, Column 1 sum: 15, Column 2 sum: 18. (Hint: The outer loop should iterate over columns, and the inner loop over rows).

Sample Answer
matrix = [[1,2,3],[4,5,6],[7,8,9]] for c in range(len(matrix[0])): col_sum = sum(row[c] for row in matrix) print(f"Column {c} sum: {col_sum}")

Question 5: The Event Logger (Combining Patterns) You are given a list of tuples: logs = [("user1", "login"), ("user2", "error"), ("user1", "error"), ("user3", "login"), ("user2", "logout")]. Write a program that:

  1. Uses a flag to check if any user had the "error" event. Print "System has errors" if true, else "System running smoothly".
  2. Uses filtering to create a new list error_logs containing only the tuples where the event is "error". Print error_logs.
  3. (Challenge) Use a nested loop or a dictionary (if you know it) to count how many events each user generated.
Sample Answer
logs = [("user1","login"), ("user2","error"), ("user1","error"), ("user3","login"), ("user2","logout")] has_error = any(event == "error" for _, event in logs) print("System has errors" if has_error else "System running smoothly") error_logs = [(u,e) for u,e in logs if e == "error"] print("Error logs:", error_logs) # Count events per user counts = {} for user, _ in logs: counts[user] = counts.get(user, 0) + 1 print("User event counts:", counts)

Congratulations! You have completed the final fundamental tutorial on loops. You now possess a toolkit of patterns (Accumulation, Searching, Flagging, Filtering) and Pythonic tools (enumerate, zip) that will allow you to tackle 80% of the looping problems you'll encounter in the real world. Combine this with the break/continue logic from Tutorial 3, and you are ready to move on to functions!

Previous | Tutorial index | Next