Pythonenumerate() & zip()

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

Python
fruits = ["apple", "banana", "cherry"]

for index, fruit in enumerate(fruits):
    print(index, fruit)

# Output:
# 0 apple
# 1 banana
# 2 cherry

By 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

Python
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. deploy
zip() — 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

Python
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: 85
zip() stops at the shortest iterable
If the iterables passed to `zip()` have different lengths, `zip()` silently stops as soon as the *shortest* one runs out — it never raises an error, so a length mismatch can quietly drop data.

Mismatched lengths get silently truncated

Python
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

Python
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 her
Unzipping — 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

Python
pairs = [("Amara", 92), ("Bilal", 78), ("Chen", 85)]

names, scores = zip(*pairs)

print(names)   # ('Amara', 'Bilal', 'Chen')
print(scores)  # (92, 78, 85)
zip() returns tuples
Notice `names` and `scores` come back as tuples, not lists. Wrap the result in `list()` — e.g. `list(names)` — if you need a mutable list instead.
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

Python
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
Tip
Reach for `enumerate()` any time you catch yourself writing `i = 0` before a loop just to track position, and reach for `zip()` any time you're indexing into two lists with the same counter variable — both are signs Python already has a cleaner built-in for what you're doing.