Loops
Python for and while loops, enumerate and zip for cleaner iteration, break and continue, and the for/else clause.
Python has two loop forms: for iterates over a collection or range, and while runs as long as a condition is true. Both support break (exit early) and continue (skip to the next iteration). Both also accept an else clause - uncommon but worth knowing.
for x in xs: works with any iterable - lists, ranges, strings, dicts, files. range(n) produces integers 0 through n-1. enumerate(xs) gives you each item paired with its index, removing the need for a separate counter. zip(a, b) steps through two iterables in parallel, pairing items at the same position.
fruits = ["apple", "banana", "cherry"]
# Basic for loop
for fruit in fruits:
print(fruit)
# range: 0, 1, 2, 3, 4
for i in range(5):
print(i)
# enumerate: index + value
for i, fruit in enumerate(fruits):
print(i, fruit) # 0 apple, 1 banana, 2 cherry
# zip: iterate two lists in parallel
prices = [1.20, 0.50, 2.00]
for fruit, price in zip(fruits, prices):
print(f"{fruit}: ${price:.2f}")while runs as long as its condition is true. Use break to exit early when a goal is reached, and continue to skip the rest of the loop body for the current iteration. A while True: loop with an explicit break is a common pattern for "keep reading until a sentinel value."
# Read lines until the user types "quit"
lines = []
while True:
line = input("> ")
if line == "quit":
break
lines.append(line)
print(f"Collected {len(lines)} line(s)")
# continue: skip blank lines
for line in lines:
if not line.strip():
continue
process(line)Both for and while accept an optional else clause. The else block runs when the loop finishes normally - that is, without hitting a break. It reads as "no early exit occurred." Most teams avoid this because readers unfamiliar with Python expect else to mean "condition was false," not "loop completed." A sentinel variable is usually clearer.
# for/else: runs the else block only when break was never hit
def find(items: list, target: int) -> str:
for item in items:
if item == target:
break
else:
return "not found"
return f"found {target}"
find([1, 2, 3], 2) # 'found 2'
find([1, 2, 3], 9) # 'not found'
# Same logic, clearer with a sentinel variable
def find_v2(items: list, target: int) -> str:
found = False
for item in items:
if item == target:
found = True
break
return f"found {target}" if found else "not found"In production
The for/else clause runs when the loop exits without break - it reads as "no match found." Most teams use a sentinel variable instead because else on a loop is widely misunderstood and will surprise reviewers unfamiliar with Python. Modifying a list while iterating it (for x in xs: xs.remove(x)) skips elements because the iterator's position shifts after each removal; iterate over a copy (for x in xs[:]:) or build a new list with a comprehension.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free