Loops: Intermediate

Airbnb's pricing engine uses zip to pair availability calendars with pricing calendars for millions of listings simultaneously, iterating both sequences in lockstep to compute dynamic rates without a single index variable. When engineers process two related data streams at the same time, zip turns a nested loop into a single clean iteration that is both faster and easier to read. The intermediate loop patterns in this lesson, including enumerate, zip, and break/continue, are the toolkit that separates readable production code from amateur scripts.

enumerate() for Index

Daily Life
Interviews

Track position and value together

The enumerate() function adds a counter to an iterable. It returns pairs of (index, value) on each iteration, giving you access to both the position and the item without manually managing an index variable.

Basic enumerate Usage

enumerate(iterable) returns an enumerate object that yields (index, item) tuples. You typically unpack these in the for statement:

1fruits = ["apple", "banana", "cherry"]
2
3print("Manual approach:")
4i = 0
5for fruit in fruits:
6 print(str(i) + ": " + fruit)
7 i += 1
8
9print()
10
11print("With enumerate:")
12for index, fruit in enumerate(fruits):
13 print(str(index) + ": " + fruit)
>>>Output
Manual approach:
0: apple
1: banana
2: cherry
 
With enumerate:
0: apple
1: banana
2: cherry

The index, fruit syntax unpacks the tuple returned by enumerate. The first value is the index, the second is the item from the list.

Starting at Any Index

By default, enumerate starts counting at 0. Use the start parameter to begin at a different number:

1tasks = ["Wake up", "Eat breakfast", "Go to work"]
2
3# Start counting from 1 (more natural for display)
4print("Daily tasks:")
5for num, task in enumerate(tasks, start=1):
6 print(str(num) + ". " + task)
7
8print()
9
10# Start from any number
11print("Continue from 10:")
12for i, task in enumerate(tasks, start=10):
13 print("Task #" + str(i) + ": " + task)
>>>Output
Daily tasks:
1. Wake up
2. Eat breakfast
3. Go to work
 
Continue from 10:
Task #10: Wake up
Task #11: Eat breakfast
Task #12: Go to work

enumerate() Patterns

enumerate is essential when you need to modify a list in place or when position matters:

1# Modify list in place
2numbers = [1, 2, 3, 4, 5]
3for i, num in enumerate(numbers):
4 numbers[i] = num * 2
5print("Doubled:", numbers)
6
7# Find index of first match
8names = ["Alice", "Bob", "Charlie", "Bob"]
9for i, name in enumerate(names):
10 if name == "Bob":
11 print("First Bob at index:", i)
12 break
13
14# Track progress in processing
15data = ["row1", "row2", "row3", "row4", "row5"]
16for i, row in enumerate(data, start=1):
17 print("Processing " + str(i) + "/" + str(len(data)) + ": " + row)
>>>Output
Doubled: [2, 4, 6, 8, 10]
First Bob at index: 1
Processing 1/5: row1
Processing 2/5: row2
Processing 3/5: row3
Processing 4/5: row4
Processing 5/5: row5
TIP
Always prefer enumerate() over range(len(list)) when you need both index and value. It's more Pythonic, more readable, and less error-prone.

Try changing the start parameter to see how enumerate adjusts the counter. Each option produces a different numbering scheme:

Fill in the Blank

> The enumerate function pairs each item with an index. Pick a start value to see how the numbering changes.

colors = ["red", "green", "blue"]
for i, c in enumerate(colors, start=):
    print(i, c)

enumerate() for Strings

Enumerate works with any iterable, including strings. This is useful for finding character positions or processing text with position awareness:
1text = "Hello World"
2
3# Find all positions of a character
4letter = "o"
5positions = []
6for i, char in enumerate(text):
7 if char == letter:
8 positions.append(i)
9print("'" + letter + "' found at positions:", positions)
10
11# Show character positions
12print()
13print("Character positions:")
14for i, char in enumerate(text):
15 if char != " ":
16 print("Position " + str(i) + ": '" + char + "'")
>>>Output
'o' found at positions: [4, 7]
 
Character positions:
Position 0: 'H'
Position 1: 'e'
Position 2: 'l'
Position 3: 'l'
Position 4: 'o'
Position 6: 'W'
Position 7: 'o'
Position 8: 'r'
Position 9: 'l'
Position 10: 'd'

enumerate() for Progress

When processing large datasets or files, enumerate helps display progress to users:
1# Simulate processing records
2records = ["record_1", "record_2", "record_3", "record_4", "record_5"]
3total = len(records)
4
5for i, record in enumerate(records, start=1):
6 percent = int((i / total) * 100)
7 print("Processing " + str(i) + "/" + str(total) + " (" + str(percent) + "%): " + record)
8
9print()
10print("All records processed!")
>>>Output
Processing 1/5 (20%): record_1
Processing 2/5 (40%): record_2
Processing 3/5 (60%): record_3
Processing 4/5 (80%): record_4
Processing 5/5 (100%): record_5
 
All records processed!

Here are the most common scenarios where enumerate() saves you from manual index tracking:

Modify list elements in place
Modify list elements in place
Use the index from enumerate to update items at their original position
Find indices of matches
Find indices of matches
Locate where specific values appear without a separate counter variable
Display progress indicators
Display progress indicators
Show the user how far along processing has reached in a large dataset
Create position-based mappings
Create position-based mappings
Build dictionaries that map indices to values using enumerate pairs

zip() Parallel Iteration

Daily Life
Interviews

Iterate multiple lists in lockstep

The zip() function combines multiple iterables element-by-element. On each iteration, it yields a tuple containing one item from each input sequence. This lets you iterate over related data in parallel.

Basic zip Usage

zip(iterable1, iterable2, ...) pairs up items from each iterable by position:

1names = ["Alice", "Bob", "Charlie"]
2ages = [25, 30, 35]
3
4# Iterate over both lists in parallel
5for name, age in zip(names, ages):
6 print(name + " is " + str(age) + " years old")
>>>Output
Alice is 25 years old
Bob is 30 years old
Charlie is 35 years old
On the first iteration, you get ("Alice", 25). On the second, ("Bob", 30). The items are paired by their position in each list.

zip() with Unequal Lengths

When sequences have different lengths, zip stops at the shortest one:
1names = ["Alice", "Bob", "Charlie", "Diana"]
2scores = [85, 92, 78]
3
4# Only 3 pairs - stops at shortest sequence
5for name, score in zip(names, scores):
6 print(name + ":", score)
7
8print()
9
10# Extra items in longer list are ignored
11letters = ["a", "b"]
12numbers = [1, 2, 3, 4, 5]
13for letter, num in zip(letters, numbers):
14 print(letter, num)
>>>Output
Alice: 85
Bob: 92
Charlie: 78
 
a 1
b 2
Knowing when to reach for enumerate versus zip versus plain iteration saves you from writing unnecessary boilerplate. Here is a concise guide.
Choosing the Right Iteration Tool
  • Need just values: plain for loop over the sequence.
  • Need index and value: enumerate() with tuple unpacking.
  • Need values from two or more lists in lockstep: zip().
  • Need index plus multiple lists: enumerate(zip(a, b)).

zip() with Multiple Lists

You can zip together any number of sequences:
1names = ["Alice", "Bob", "Charlie"]
2ages = [25, 30, 35]
3cities = ["NYC", "LA", "Chicago"]
4
5for name, age, city in zip(names, ages, cities):
6 print(name + ", " + str(age) + ", lives in " + city)
7
8print()
9
10# Four lists
11ids = [1, 2, 3]
12first = ["Alice", "Bob", "Charlie"]
13last = ["Smith", "Jones", "Brown"]
14scores = [85, 92, 78]
15
16for id, f, l, s in zip(ids, first, last, scores):
17 print("ID " + str(id) + ": " + f + " " + l + " scored " + str(s))
>>>Output
Alice, 25, lives in NYC
Bob, 30, lives in LA
Charlie, 35, lives in Chicago
 
ID 1: Alice Smith scored 85
ID 2: Bob Jones scored 92
ID 3: Charlie Brown scored 78

Dicts from zip()

A common pattern is using zip to create dictionaries from two lists:
1keys = ["name", "age", "city"]
2values = ["Alice", 25, "NYC"]
3
4# Create dict from parallel lists
5person = dict(zip(keys, values))
6print(person)
7
8# Useful for configuration
9settings_names = ["debug", "timeout", "retries"]
10settings_values = [True, 30, 3]
11config = dict(zip(settings_names, settings_values))
12print(config)
>>>Output
{'name': 'Alice', 'age': 25, 'city': 'NYC'}
{'debug': True, 'timeout': 30, 'retries': 3}

Combining zip and enumerate

You can combine zip and enumerate when you need index information while iterating over multiple sequences in parallel:
1names = ["Alice", "Bob", "Charlie"]
2scores = [85, 92, 78]
3
4# Get index while zipping
5print("Ranked results:")
6for i, (name, score) in enumerate(zip(names, scores), start=1):
7 print(str(i) + ". " + name + ": " + str(score))
8
9print()
10
11# Compare adjacent pairs in a list
12values = [10, 20, 15, 25, 30]
13print("Consecutive differences:")
14for i, (a, b) in enumerate(zip(values, values[1:])):
15 diff = b - a
16 direction = "up" if diff > 0 else "down"
17 print("Position " + str(i) + " to " + str(i+1) + ": " + direction + " " + str(abs(diff)))
>>>Output
Ranked results:
1. Alice: 85
2. Bob: 92
3. Charlie: 78
 
Consecutive differences:
Position 0 to 1: up 10
Position 1 to 2: down 5
Position 2 to 3: up 10
Position 3 to 4: up 5

Transposing with zip

A clever use of zip with the * unpacking operator transposes rows and columns in a matrix:

1# Original matrix (3 rows, 4 columns)
2matrix = [
3 [1, 2, 3, 4],
4 [5, 6, 7, 8],
5 [9, 10, 11, 12]
6]
7
8print("Original:")
9for row in matrix:
10 print(row)
11
12# Transpose using zip(*matrix)
13transposed = list(zip(*matrix))
14
15print()
16print("Transposed (4 rows, 3 columns):")
17for row in transposed:
18 print(list(row))
>>>Output
Original:
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
 
Transposed (4 rows, 3 columns):
[1, 5, 9]
[2, 6, 10]
[3, 7, 11]
[4, 8, 12]
Python Quiz

> Pair names with their scores so each element is a tuple. Pick the built-in that combines two sequences element-by-element, and the one that counts the resulting pairs.

names = ["Alice", "Bob"]
scores = [85, 92]
pairs = list(___(names, scores))
print(pairs[0])
print(___(pairs))
enumerate
zip
map
sum
len

zip() is lazy: it does not build a list in memory until you ask for it. Wrap it in list() to materialize all pairs, or iterate directly in a for loop to process one pair at a time.

enumerate() and zip() cover the two most common iteration patterns: working with a single sequence with positional context, or working with multiple parallel sequences together.

When zipping sequences of different lengths, zip() stops at the shortest one. Use itertools.zip_longest() if you need to process all elements and fill missing values with a default.

Iterating Dict Items

Daily Life
Interviews

Loop through dict keys, values, or both

Dictionaries provide several methods for iteration: you can loop over just keys, just values, or key-value pairs together. Each approach is useful in different situations.

Iterating Over Keys

By default, iterating over a dictionary yields its keys:
1person = {"name": "Alice", "age": 25, "city": "NYC"}
2
3# Default iteration gives keys
4print("Keys:")
5for key in person:
6 print(key)
7
8print()
9
10# Explicitly using .keys() - same result
11print("Using .keys():")
12for key in person.keys():
13 print(key)
>>>Output
Keys:
name
age
city
 
Using .keys():
name
age
city

Iterating Over Values

Use .values() to iterate over just the values:

1scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
2
3# Iterate over values only
4print("Scores:")
5for score in scores.values():
6 print(score)
7
8print()
9
10# Calculate total
11total = 0
12for score in scores.values():
13 total += score
14print("Total:", total)
15print("Average:", total / len(scores))
>>>Output
Scores:
85
92
78
 
Total: 255
Average: 85.0

Iterating Key-Value Pairs

Use .items() to get both key and value together. This is the most commonly used pattern:

1person = {"name": "Alice", "age": 25, "city": "NYC"}
2
3# Key-value pairs
4for key, value in person.items():
5 print(key + ": " + str(value))
6
7print()
8
9# Practical: format configuration
10config = {"debug": True, "timeout": 30, "max_retries": 3}
11print("Current settings:")
12for setting, value in config.items():
13 print(" " + setting + " = " + str(value))
>>>Output
name: Alice
age: 25
city: NYC
 
Current settings:
debug = True
timeout = 30
max_retries = 3
Dictionary Iteration
  • for key in dict: - iterate keys
  • for val in dict.values(): - iterate values
  • for k, v in dict.items(): - key-value pairs
When to Use Each
  • Keys only: checking membership
  • Values only: aggregations
  • Items: most common, need both

Filtering Dict Entries

Common patterns when working with dictionary iteration:
1scores = {"Alice": 85, "Bob": 92, "Charlie": 78, "Diana": 95}
2
3# Filter to passing scores (>= 80)
4passing = {}
5for name, score in scores.items():
6 if score >= 80:
7 passing[name] = score
8print("Passing:", passing)
9
10# Transform values
11curved = {}
12for name, score in scores.items():
13 curved[name] = min(score + 5, 100)
14print("Curved:", curved)
15
16# Find key with max value
17max_name = None
18max_score = -1
19for name, score in scores.items():
20 if score > max_score:
21 max_score = score
22 max_name = name
23print("Top scorer:", max_name, "with", max_score)
>>>Output
Passing: {'Alice': 85, 'Bob': 92, 'Diana': 95}
Curved: {'Alice': 90, 'Bob': 97, 'Charlie': 83, 'Diana': 100}
Top scorer: Diana with 95

Iterating Nested Dicts

Real-world data often involves dictionaries containing other dictionaries. You can use nested loops to access all levels:
1# Nested dictionary structure
2company = {
3 "engineering": {
4 "Alice": 95000,
5 "Bob": 85000,
6 },
7 "marketing": {
8 "Charlie": 75000,
9 "Diana": 80000,
10 }
11}
12
13# Iterate through nested structure
14print("Company salaries:")
15for department, employees in company.items():
16 print()
17 print(department.upper() + ":")
18 for name, salary in employees.items():
19 print(" " + name + ": " + str(salary))
20
21# Calculate totals per department
22print()
23print("Department totals:")
24for department, employees in company.items():
25 total = sum(employees.values())
26 print(department + ": " + str(total))
>>>Output
Company salaries:
 
ENGINEERING:
Alice: 95000
Bob: 85000
 
MARKETING:
Charlie: 75000
Diana: 80000
 
Department totals:
engineering: 180000
marketing: 155000

Building Dicts from Loops

You can construct dictionaries dynamically while iterating:
1# Build a frequency counter
2text = "hello world"
3frequency = {}
4for char in text:
5 if char in frequency:
6 frequency[char] += 1
7 else:
8 frequency[char] = 1
9
10print("Character frequencies:", frequency)
11
12# Group items by property
13users = [
14 {"name": "Alice", "role": "admin"},
15 {"name": "Bob", "role": "user"},
16 {"name": "Charlie", "role": "admin"},
17 {"name": "Diana", "role": "user"},
18]
19
20by_role = {}
21for user in users:
22 role = user["role"]
23 if role not in by_role:
24 by_role[role] = []
25 by_role[role].append(user["name"])
26
27print()
28print("Users by role:", by_role)
>>>Output
Character frequencies: {'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}
 
Users by role: {'admin': ['Alice', 'Charlie'], 'user': ['Bob', 'Diana']}
TIP
The pattern of checking if a key exists before updating is common. Python's collections.defaultdict and dict.setdefault() methods can simplify this, which you'll learn in more advanced lessons.
When iterating over dictionaries, follow these practices to keep your code safe and readable:
Do
  • Use .items() when you need both key and value
  • Use dict comprehension for filtering
  • Iterate over list(dict.keys()) to modify
  • Use descriptive names for k, v
Don't
  • Add or delete keys while iterating directly
  • Use dict[key] inside a for-key loop when .items() works
  • Assume insertion order in Python < 3.7
  • Modify values through a values() view
Python Quiz

> Sum the numeric values and collect the key names from a dictionary. Pick the accessor that returns just the numbers, and the one that returns just the names.

data = {"x": 10, "y": 20, "z": 30}
total = sum(data.___())
names = list(data.___())
print(total)
print(names[0])
values
items
keys
keys
values

Choosing the right dictionary view method keeps your intent clear. Use .keys() when only the labels matter, .values() when only the data matters, and .items() when you need both together.

Dictionary views are live: if you add or remove keys after creating a view, the view reflects those changes immediately. Convert to a list if you need a stable snapshot of the keys or values.
The grouping pattern, where you check if a key exists before appending to its list, is one of the most common dictionary-building idioms in Python and appears frequently in data aggregation tasks.

Nested Loops

Daily Life
Interviews

Process grids and generate combinations

A nested loop is a loop inside another loop. The inner loop runs completely for each iteration of the outer loop. This pattern is essential for working with multi-dimensional data structures and generating combinations.

Basic Nested Loop

The inner loop completes all its iterations before the outer loop moves to the next item:
1for i in range(1, 4):
2 print("Outer:", i)
3 for j in range(1, 3):
4 print(" Inner:", j)
5 print()
>>>Output
Outer: 1
Inner: 1
Inner: 2
 
Outer: 2
Inner: 1
Inner: 2
 
Outer: 3
Inner: 1
Inner: 2
 
The outer loop runs 3 times. For each outer iteration, the inner loop runs 2 times. Total iterations: 3 x 2 = 6.

Working with 2D Data

Nested loops are natural for processing matrices (lists of lists):
1# 2D grid (list of lists)
2grid = [
3 [1, 2, 3],
4 [4, 5, 6],
5 [7, 8, 9]
6]
7
8# Process each cell
9print("Grid contents:")
10for row in grid:
11 for cell in row:
12 print(cell, end=" ")
13 print()
14
15print()
16
17# Sum all values
18total = 0
19for row in grid:
20 for cell in row:
21 total += cell
22print("Sum:", total)
23
24# Access with indices
25print()
26print("Using indices:")
27for i in range(len(grid)):
28 for j in range(len(grid[i])):
29 print("grid[" + str(i) + "][" + str(j) + "] =", grid[i][j])
>>>Output
Grid contents:
1 2 3
4 5 6
7 8 9
 
Sum: 45
 
Using indices:
grid[0][0] = 1
grid[0][1] = 2
grid[0][2] = 3
grid[1][0] = 4
grid[1][1] = 5
grid[1][2] = 6
grid[2][0] = 7
grid[2][1] = 8
grid[2][2] = 9

Generating Combinations

Nested loops naturally generate all combinations of elements:
1colors = ["red", "blue"]
2sizes = ["S", "M", "L"]
3
4# All color-size combinations
5print("Products:")
6for color in colors:
7 for size in sizes:
8 print(color + "-" + size)
9
10print()
11
12# Multiplication table
13print("Multiplication table (1-3):")
14for i in range(1, 4):
15 row = ""
16 for j in range(1, 4):
17 row += str(i * j) + " "
18 print(row)
>>>Output
Products:
red-S
red-M
red-L
blue-S
blue-M
blue-L
 
Multiplication table (1-3):
1 2 3
2 4 6
3 6 9
TIP
Nested loops multiply iterations. A loop of 1000 inside a loop of 1000 runs 1,000,000 times. Be mindful of performance with deeply nested loops over large datasets.

Triangle and Pyramid

Nested loops where the inner loop depends on the outer loop create triangle patterns. This is common for comparisons and hierarchical displays:
1print("Triangle:")
2for i in range(1, 6):
3 row = ""
4 for j in range(i):
5 row += "* "
6 print(row)
7
8print()
9
10# Pairs without repetition
11items = ["A", "B", "C", "D"]
12print("Unique pairs:")
13for i in range(len(items)):
14 for j in range(i + 1, len(items)):
15 print(items[i] + "-" + items[j])
>>>Output
Triangle:
*
* *
* * *
* * * *
* * * * *
 
Unique pairs:
A-B
A-C
A-D
B-C
B-D
C-D
In the unique pairs example, starting the inner loop at i+1 ensures each pair is only counted once (A-B but not B-A).
This nested loop has a bug that causes incorrect output. Can you find and remove the extra tile?
Debug Challenge

> This nested loop should print a multiplication table, but the formula is wrong. Remove the extra tile to fix it.

LogicError: printing i*i*j instead of i*j. The table values are wrong.

Searching in 2D Structures

Nested loops are essential for searching through two-dimensional data structures:
1# Find target in 2D grid
2grid = [
3 [1, 2, 3],
4 [4, 5, 6],
5 [7, 8, 9]
6]
7
8target = 5
9found_at = None
10
11for row_idx, row in enumerate(grid):
12 for col_idx, value in enumerate(row):
13 if value == target:
14 found_at = (row_idx, col_idx)
15 break
16 if found_at:
17 break
18
19print("Found", target, "at position:", found_at)
20
21# Find all positions matching condition
22positions = []
23for row_idx, row in enumerate(grid):
24 for col_idx, value in enumerate(row):
25 if value % 2 == 0:
26 positions.append((row_idx, col_idx, value))
27
28print()
29print("Even numbers found:")
30for row, col, val in positions:
31 print(" grid[" + str(row) + "][" + str(col) + "] = " + str(val))
>>>Output
Found 5 at position: (1, 1)
 
Even numbers found:
grid[0][1] = 2
grid[1][0] = 4
grid[1][2] = 6
grid[2][1] = 8
Nested loops unlock a wide range of algorithmic patterns. Each pattern pairs an outer traversal with an inner operation:
01
Grid operations
Process every cell in a 2D matrix using row and column loops
02
Combinations
Generate all pairs from two or more sets of values
03
Pattern printing
Build triangle, pyramid, and diamond shapes row by row
04
Multi-dim search
Find a target value by scanning rows then columns
05
Pair comparisons
Check every pair of items for duplicates or relationships

Loop else Clauses

Daily Life
Interviews

Detect when a search finds nothing

Python has a unique feature: you can attach an else clause to loops. The else block executes when the loop completes normally (without hitting a break). If break terminates the loop, the else block is skipped.

The for-else Pattern

The else block runs only if the loop completed without breaking:
1# Search with for-else
2numbers = [1, 3, 5, 7, 9]
3
4# Looking for an even number
5for num in numbers:
6 if num % 2 == 0:
7 print("Found even:", num)
8 break
9else:
10 print("No even numbers found")
11
12print()
13
14numbers2 = [1, 3, 4, 7, 9]
15
16for num in numbers2:
17 if num % 2 == 0:
18 print("Found even:", num)
19 break
20else:
21 print("No even numbers found")
>>>Output
No even numbers found
 
Found even: 4

Think of for-else as "for...else if no break". In the first loop, no even number exists, so the loop completes normally and else runs. In the second loop, break executes, so else is skipped.

The while-else Pattern

The else clause works identically with while loops:
1# Try to find a value
2target = 42
3attempts = 0
4max_attempts = 3
5current = 10
6
7result = None
8while attempts < max_attempts:
9 attempts += 1
10 current += 10
11 print("Attempt", attempts, "- current value:", current)
12 if current == target:
13 print("Found target!")
14 break
15else:
16 print("Target not found after", max_attempts, "attempts")
17
18print()
19
20# Version where we find it
21target = 30
22attempts = 0
23current = 10
24
25while attempts < max_attempts:
26 attempts += 1
27 current += 10
28 print("Attempt", attempts, "- current value:", current)
29 if current == target:
30 print("Found target!")
31 break
32else:
33 print("Target not found")
>>>Output
Attempt 1 - current value: 20
Attempt 2 - current value: 30
Attempt 3 - current value: 40
Target not found after 3 attempts
 
Attempt 1 - current value: 20
Attempt 2 - current value: 30
Found target!

Practical Use Cases

Loop-else is ideal for search patterns where you need to know if the search succeeded:
searchvalidateprimeretry
search
Find an item
else runs if not found
validate
Check all items
else confirms all passed
prime
Divisor search
else means no divisors
retry
Attempt loops
else means all retries failed
The prime number check is a classic example. The loop searches for a divisor. If it finds one, it breaks. If the loop finishes without breaking, the else clause confirms the number is prime:
1# Check if number is prime
2def is_prime(n):
3 if n < 2:
4 return False
5 for i in range(2, int(n ** 0.5) + 1):
6 if n % i == 0:
7 return False
8 return True
9
10# Using loop-else for same logic
11n = 17
12for i in range(2, int(n ** 0.5) + 1):
13 if n % i == 0:
14 print(n, "is not prime")
15 break
16else:
17 print(n, "is prime")
>>>Output
17 is prime
Mastering loop patterns helps you write more efficient and expressive code. Put these techniques to the test with hands-on challenges in the Python Builder.
PUTTING IT ALL TOGETHER

> You are a data engineer at Salesforce processing multi-dimensional user activity logs, pairing each user with their corresponding event sequence to calculate weekly engagement scores across product lines.

enumerate() pairs each user record with its position index so you can track progress and log which record triggered an anomaly.
zip() links the user list with the parallel event-sequence list so each engagement calculation draws from both in a single loop.
Nested loops iterate over each user's individual events within the outer pass over all users to accumulate weekly scores.
The loop else clause signals cleanly when a full user scan completes without finding an expected anchor event to score against.
KEY TAKEAWAYS
enumerate(sequence) yields (index, value) pairs; use start=1 to begin at 1
zip(seq1, seq2, ...) pairs items from multiple sequences; stops at shortest
Dictionary iteration: .keys(), .values(), .items() for key-value pairs
Nested loops multiply iterations; inner loop completes fully per outer iteration
Loop else runs only if loop completes without break; great for searches
Use dict(zip(keys, values)) to create dictionaries from parallel lists
Prefer enumerate() over range(len()) for cleaner code
Be mindful of performance with nested loops over large datasets

Powerful iteration techniques

Category
Python
Difficulty
intermediate
Duration
36 minutes
Challenges
0 hands-on challenges

Topics covered: enumerate() for Index, zip() Parallel Iteration, Iterating Dict Items, Nested Loops, Loop else Clauses

Lesson Sections

  1. enumerate() for Index (concepts: pyEnumerate)

    Basic enumerate Usage Starting at Any Index enumerate() Patterns enumerate() for Strings Enumerate works with any iterable, including strings. This is useful for finding character positions or processing text with position awareness: enumerate() for Progress When processing large datasets or files, enumerate helps display progress to users:

  2. zip() Parallel Iteration (concepts: pyZip)

    Basic zip Usage On the first iteration, you get ("Alice", 25). On the second, ("Bob", 30). The items are paired by their position in each list. zip() with Unequal Lengths When sequences have different lengths, zip stops at the shortest one: Knowing when to reach for enumerate versus zip versus plain iteration saves you from writing unnecessary boilerplate. Here is a concise guide. zip() with Multiple Lists You can zip together any number of sequences: Dicts from zip() A common pattern is using z

  3. Iterating Dict Items (concepts: pyDictIterate)

    Dictionaries provide several methods for iteration: you can loop over just keys, just values, or key-value pairs together. Each approach is useful in different situations. Iterating Over Keys By default, iterating over a dictionary yields its keys: Iterating Over Values Iterating Key-Value Pairs Filtering Dict Entries Common patterns when working with dictionary iteration: Iterating Nested Dicts Real-world data often involves dictionaries containing other dictionaries. You can use nested loops t

  4. Nested Loops (concepts: pyNestedLoops)

    A nested loop is a loop inside another loop. The inner loop runs completely for each iteration of the outer loop. This pattern is essential for working with multi-dimensional data structures and generating combinations. Basic Nested Loop The inner loop completes all its iterations before the outer loop moves to the next item: The outer loop runs 3 times. For each outer iteration, the inner loop runs 2 times. Total iterations: 3 x 2 = 6. Working with 2D Data Nested loops are natural for processin

  5. Loop else Clauses (concepts: pyLoopElse)

    The for-else Pattern The else block runs only if the loop completed without breaking: The while-else Pattern The else clause works identically with while loops: Practical Use Cases Loop-else is ideal for search patterns where you need to know if the search succeeded: The prime number check is a classic example. The loop searches for a divisor. If it finds one, it breaks. If the loop finishes without breaking, the else clause confirms the number is prime: Mastering loop patterns helps you write m