GICT knowledge base
Python Nested Loops: Pairs, Rows, break and Common Mistakes
Published
A nested loop is a loop inside another loop. Use it when each outer item has several inner items to process, such as rows and cells or service groups and their members. The inner work runs for each outer iteration, but a for loop does not require you to know the input length in advance.
The six examples below run independently with Python 3.12.14. They operate on small in-memory fixtures, not real servers or files.
Generate every pair with two loops
For two complete, independent collections of sizes n and m, these loops visit n * m pairs. The first host is paired with every service before the next host is used.
hosts = ["node-a", "node-b"]
services = ["web", "mail", "dns"]
pairs = []
for host in hosts:
for service in services:
pairs.append((host, service))
assert pairs == [
("node-a", "web"), ("node-a", "mail"), ("node-a", "dns"),
("node-b", "web"), ("node-b", "mail"), ("node-b", "dns"),
]
assert len(pairs) == len(hosts) * len(services)
print(pairs)
Traverse rows without assuming a rectangle
Not every nested list has equally sized rows. Loop over each row’s actual contents rather than using the first row’s length for every row. Here, enumerate() also records positions, including the fact that the empty second row contributes no cells.
rows = [[8, 12], [], [5]]
cells = []
for row_index, row in enumerate(rows):
for column_index, value in enumerate(row):
cells.append((row_index, column_index, value))
assert cells == [(0, 0, 8), (0, 1, 12), (2, 0, 5)]
assert sum(value for _, _, value in cells) == 25
print(cells)
Reset the inner counter in nested while loops
The inner counter must start again for each row. If you initialize it only before the outer loop, it is already at its stopping value when the second row starts. Every path through either loop must eventually make progress or leave the loop.
positions = []
row = 0
while row < 2:
column = 0
while column < 3:
positions.append((row, column))
column += 1
row += 1
assert positions == [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
print(positions)
Know which loop break exits
break exits only the nearest enclosing loop. continue likewise advances that loop, not every surrounding loop. To stop an entire search, a function can return its answer from inside the nested loops.
first_columns = []
for row in range(2):
for column in range(3):
first_columns.append((row, column))
break
assert first_columns == [(0, 0), (1, 0)]
def locate(rows, target):
for row_index, row in enumerate(rows):
for column_index, value in enumerate(row):
if value == target:
return row_index, column_index
return None
assert locate([[2, 4], [4, 8]], 4) == (0, 1)
assert locate([[2], []], 7) is None
assert locate([], 7) is None
print(first_columns)
Do not substitute zip for a Cartesian product
zip() pairs corresponding positions; it does not generate every combination. By default it stops at the shortest input. strict=True detects unequal lengths while the result is consumed. itertools.product() expresses every combination, but first consumes its input iterables into internal pools, so it is not a solution for infinite input or for eliminating the number of combinations.
from itertools import product
hosts = ["node-a", "node-b"]
ports = [80, 443, 8443]
assert list(zip(hosts, ports)) == [("node-a", 80), ("node-b", 443)]
assert list(product(hosts, ports)) == [
(host, port) for host in hosts for port in ports
]
assert len(list(product(hosts, ports))) == 6
try:
list(zip(hosts, ports, strict=True))
except ValueError:
pass
else:
raise AssertionError("Unequal input lengths were not detected")
print(list(zip(hosts, ports)))
Process dictionaries and improve the right operation
A grouped dictionary can be traversed with items(), then each group’s members. If the task is membership testing, preparing a set may avoid repeatedly scanning a list. Verify that ordering and duplicate behavior remain correct before calling a rewrite an optimization.
groups = {"frontend": ["web", "cache"], "backend": ["db", "web"]}
allowed = {"web", "db"}
selected = []
for group, members in groups.items():
for member in members:
if member in allowed:
selected.append((group, member))
assert selected == [("frontend", "web"), ("backend", "db"), ("backend", "web")]
print(selected)
Two nested loops are not automatically quadratic in a single input size. Independent bounds give n * m visits; a ragged structure visits the sum of its row lengths; early exits can reduce the work. The cost of each body also matters. Printing or storing every combination still costs work even if a comprehension or iterator makes the source shorter.
Verification: All six blocks ran independently, including empty and ragged inputs, first-match behavior and unequal-length rejection. This is correctness testing, not a speed benchmark. References: loop semantics and itertools.product. See also loop basics.
Related guides
How to Deploy a FastAPI App with Gunicorn and Nginx
Deploy FastAPI behind Nginx with Gunicorn and systemd. Separate application packages, check HTTPS and verify worker replacement and application health.
PythonLearn How to Modify Python Dictionary Values Easily
Learn how to modify Python dictionary values effectively with various methods, including direct assignment, update(), and comprehensions, along with common mistakes to...
PythonPython Set Copy Tutorial: Avoid Common Mistakes and Data Errors
Learn how to copy sets in Python safely and efficiently while avoiding common mistakes. Explore methods like copy(), set(), and deepcopy().
PythonDifferent Methods to Loop Through Sets in Python (Beginner-Friendly Guide)
Learn diverse methods to loop through sets in Python, including practical examples and use cases, ideal for beginners looking to master set operations.
