enumerate() and zip() in Python
enumerate() gives you the index and the value together while looping, and zip() iterates over two or more sequences in parallel. Between them they replace almost every range(len(x)) loop you'd otherwise write.
How Does enumerate() Work?
enumerate(iterable) yields (index, value) pairs, so you unpack both in the for line instead of indexing manually.
tasks = ["load", "clean", "export"]
for i, task in enumerate(tasks):
print(f"{i}: {task}")
# 0: load
# 1: clean
# 2: export
# Instead of the clunky:
for i in range(len(tasks)):
print(f"{i}: {tasks[i]}")
What Does the start Parameter Do?
start= sets the first index, which is perfect for human-facing numbering that begins at 1 โ no i + 1 scattered through the loop body.
for line_no, row in enumerate(tasks, start=1):
print(f"Step {line_no}: {row}")
# Step 1: load
# Step 2: clean
# Step 3: export
How Does zip() Iterate in Parallel?
zip(a, b) yields tuples pairing the first elements, then the second, and so on, stopping at the shortest input. It takes any number of iterables.
names = ["Alice", "Bob", "Cara"]
scores = [91, 78, 85]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Alice: 91
# Bob: 78
# Cara: 85
What Does strict=True Do in Python 3.10+?
By default zip silently truncates to the shortest iterable, which hides length-mismatch bugs; strict=True (Python 3.10+) raises ValueError instead.
ids = [1, 2, 3]
names = ["Alice", "Bob"] # oops, one short
list(zip(ids, names)) # [(1, 'Alice'), (2, 'Bob')] โ 3 vanished
list(zip(ids, names, strict=True)) # ValueError: argument 2 is shorter
Use strict=True whenever the inputs are supposed to be the same length.
How Do You Build a Dict from Two Lists?
dict(zip(keys, values)) pairs the lists directly into a dictionary โ the idiomatic one-liner for turning parallel lists into a lookup.
headers = ["id", "name", "score"]
row = [7, "Alice", 91]
record = dict(zip(headers, row))
# {'id': 7, 'name': 'Alice', 'score': 91}
# Unzip with the star operator:
pairs = [("a", 1), ("b", 2)]
letters, numbers = zip(*pairs) # ('a', 'b'), (1, 2)
Common Pitfalls
- Silent truncation: plain
zipdrops trailing items from longer inputs. Usestrict=Trueoritertools.zip_longestwhen lengths may differ. - Single-use iterators: both return lazy iterators.
list(z)a second time gives[]โ materialize once if you need to reuse. - enumerate on dicts: it numbers the keys, it doesn't give you values. For key-value loops use
d.items(). - Combining both: the order is
enumerate(zip(a, b)), unpacked asfor i, (x, y) in ...โ note the inner parentheses.
These patterns come up constantly in loop-heavy code โ see Python for loops for the fundamentals, and list comprehensions for turning zip loops into one-liners.
Pro Tip: If you're writing range(len(x)), stop and ask which tool you actually need: value only โ plain for, index too โ enumerate, second list โ zip. One of the three covers nearly every case.