Innovate | Connect | Transform
ContactService statusUSD $

GICT knowledge base

Python Nested Loops: Pairs, Rows, break and Common Mistakes

Published

Python

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

Account services are unavailable until the production backend is configured.