enumerate() & zip()
Two of the most useful built-in helpers for loops are enumerate() and zip(). enumerate() solves the "I need the index and the value" problem without manually tracking a counter. zip() solves the "I need to walk through two or more lists in lockstep" problem without manually indexing into each one. Both make loops read closer to plain English, and both show up constantly in real code.
enumerate() — index and value together
Looping over a list with a manual counter (i = 0, then i += 1 at the end of the loop) works, but it's easy to get wrong and it clutters the loop. enumerate() wraps any iterable and yields (index, value) pairs instead.
enumerate() basics
fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
print(index, fruit)
# Output:
# 0 apple
# 1 banana
# 2 cherryBy default counting starts at 0, but you can pass start to begin anywhere else — handy for human-friendly, 1-based numbering.
enumerate() with a custom start
tasks = ["write tests", "fix bug", "deploy"]
for number, task in enumerate(tasks, start=1):
print(f"{number}. {task}")
# Output:
# 1. write tests
# 2. fix bug
# 3. deployzip() — pairing multiple iterables
zip() takes two or more iterables and walks through them together, producing tuples that combine the corresponding elements from each one. It's the natural way to loop over "parallel" lists — for example, a list of names and a matching list of scores.
Pairing names with scores
names = ["Amara", "Bilal", "Chen"]
scores = [92, 78, 85]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Output:
# Amara: 92
# Bilal: 78
# Chen: 85Mismatched lengths get silently truncated
names = ["Amara", "Bilal", "Chen", "Deepa"]
scores = [92, 78, 85] # one shorter than names
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Output:
# Amara: 92
# Bilal: 78
# Chen: 85
# ("Deepa" is silently dropped -- no error, no warning)If you need every element from the longer iterable, padding the shorter one with a fill value, use itertools.zip_longest instead of the built-in zip.
itertools.zip_longest as the escape hatch
from itertools import zip_longest
names = ["Amara", "Bilal", "Chen", "Deepa"]
scores = [92, 78, 85]
for name, score in zip_longest(names, scores, fillvalue="N/A"):
print(f"{name}: {score}")
# Output includes Deepa: N/A instead of dropping herUnzipping — splitting pairs back apart
zip() can also run in reverse. If you have a list of pairs (tuples) and want to split it back into separate lists, unpack it with the * operator: zip(*pairs).
Unzipping a list of pairs
pairs = [("Amara", 92), ("Bilal", 78), ("Chen", 85)]
names, scores = zip(*pairs)
print(names) # ('Amara', 'Bilal', 'Chen')
print(scores) # (92, 78, 85)Combining enumerate() and zip()
These two are frequently used together: zip() pairs up the parallel data, and enumerate() numbers the resulting pairs for a human-readable report.
Numbered report combining both
names = ["Amara", "Bilal", "Chen"]
scores = [92, 78, 85]
for rank, (name, score) in enumerate(zip(names, scores), start=1):
print(f"{rank}. {name} scored {score}")
# Output:
# 1. Amara scored 92
# 2. Bilal scored 78
# 3. Chen scored 85