When and how do you use enumerate() and zip() in Python, and what are common mistakes when using them together?
enumerate() pairs each element with its index without maintaining a manual counter. zip() pairs elements from multiple iterables together. Both return lazy iterators, so they compose efficiently. The key trap with zip() is silent truncation when iterables differ in length.
How to think about it
The interviewer wants to see you reach for enumerate and zip instead of hand-tracking an index — and to know the one trap that bites, zip’s silent truncation. Both are lazy iterators: they yield tuples on demand, with O(1) memory no matter how big the inputs, which is exactly what lets them compose into pipelines.
enumerate hands you (index, value) so you never write i = 0; ...; i += 1. zip walks several iterables in lockstep, pairing their elements. Put zip inside enumerate when you want a position and paired values; and use itertools.zip_longest when the iterables might differ in length and you can’t afford to drop the tail.
A worked example
fruits = ["apple", "banana", "cherry"]
scores = [95, 80, 88]
print("--- enumerate (start=1) ---")
for i, fruit in enumerate(fruits, start=1): # start= shifts the index
print(f" {i}. {fruit}")
print()
print("--- zip to build a dict ---")
mapping = dict(zip(fruits, scores)) # one-liner dict from two lists
for fruit, score in mapping.items():
print(f" {fruit}: {score}")
print()
print("--- enumerate + zip together ---")
for i, (fruit, score) in enumerate(zip(fruits, scores)):
print(f" [{i}] {fruit} -> {score}")
print()
print("--- zip_longest with unequal lists ---")
from itertools import zip_longest
for x, y in zip_longest([1, 2, 3], [10, 20], fillvalue=0):
print(f" {x} + {y} = {x + y}")
--- enumerate (start=1) ---
1. apple
2. banana
3. cherry
--- zip to build a dict ---
apple: 95
banana: 80
cherry: 88
--- enumerate + zip together ---
[0] apple -> 95
[1] banana -> 80
[2] cherry -> 88
--- zip_longest with unequal lists ---
1 + 10 = 11
2 + 20 = 22
3 + 0 = 3
Two idioms worth lifting out. dict(zip(keys, values)) is the cleanest way to build a mapping from two parallel lists. And enumerate(zip(...)) is the right nesting when you want an index over paired items — zip first to pair them, enumerate outside to number them. In the zip_longest block, watch the third row: the shorter list ran out, so fillvalue=0 stepped in instead of the pair being dropped.
The idea underneath
Because both are lazy, chaining them — enumerate(zip(a, b)) — builds a streaming pipeline that never materialises an intermediate list. They add a constant sliver of overhead and otherwise get out of the way.