Skip to content
VLSI Mentor

Python Part I · Module 10 · Loops

enumerate() and zip()

Chapter 10.4 ended by saying that for i in range(len(x)) is almost always a sign you wanted something else. This chapter is the something else. Two situations account for nearly every index an engineer writes by hand: wanting a running number alongside each item, and wanting to walk two collections together because their entries correspond. Python supplies a helper for each, and both do more than shorten the code — they remove a class of mistake that a hand-maintained counter makes available. One of the two also has a sharp edge that silently discards data, which is worth more of your attention than its syntax.

Foundation16 min readPythonPart IFundamentalsLoopsenumeratezip

Part I · Module 10 · Chapter 10.9 · enumerate() and zip()

1. The Counter You Maintain by Hand

A numbered report needs a running number, and the obvious way to get one is to keep it yourself:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset", "burst"]

i = 1
for test in tests:
    print(i, test)
    i += 1
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 smoke
2 reset
3 burst

Three lines of that program are about the number rather than the work: create it, print it, advance it. Two of them can go wrong independently, and Section 5 shows one doing so.

2. enumerate()

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset", "burst"]

for i, test in enumerate(tests, start=1):
    print(i, test)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 smoke
2 reset
3 burst

enumerate() walks the collection and supplies a number alongside each item. There is no counter to create and none to advance — the number comes from the iteration itself.

Without start, it counts from zero, which is what you want when the number is a position:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset", "burst"]

for i, test in enumerate(tests):
    print(i, test)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0 smoke
1 reset
2 burst

3. What a Pass Hands You

enumerate() produces a two-field tuple per pass, and the two names before in unpack it — Chapter 6.5's mechanism again:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]

print(list(enumerate(tests)))
print(list(enumerate(tests, start=1)))

for pair in enumerate(tests):
    print(pair, type(pair))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[(0, 'smoke'), (1, 'reset')]
[(1, 'smoke'), (2, 'reset')]
(0, 'smoke') <class 'tuple'>
(1, 'reset') <class 'tuple'>

Taking it as a single name shows what is really being handed over; taking it as two is how you always write it.

4. Replacing range(len(...))

The pattern Chapter 10.4 warned about, and its replacement:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset", "burst"]

for i in range(len(tests)):
    print(i, tests[i])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0 smoke
1 reset
2 burst
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset", "burst"]

for i, test in enumerate(tests):
    print(i, test)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0 smoke
1 reset
2 burst

The second has no lookup, cannot go out of range, and keeps working if tests becomes a tuple or a string. Whenever range(len(...)) appears, enumerate() is the question to ask first.

5. The Mistake a Hand Counter Makes Available

The real argument for enumerate() is not brevity. It is that a counter you advance yourself can be advanced in the wrong place:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
tests = ["smoke", "reset", "burst"]

i = 1
for test in tests:
    if test.startswith("s"):
        print(i, test)
        i += 1

print("counter ended at", i)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 smoke
counter ended at 2

The intent was number every test, print only the ones starting with s. The increment sits inside the if, so the counter advances only on matching items — it is now counting matches rather than position, and after three tests it reads 2.

This compiles, runs, raises nothing, and produces numbers that look plausible. With enumerate() it cannot happen, because the number is produced by the iteration and the body has no say in it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset", "burst"]

for i, test in enumerate(tests, start=1):
    if test.startswith("s"):
        print(i, test)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 smoke

Here i is the position in the suite whatever the body does. If you wanted a count of matches instead, that is a separate counter and Chapter 10.10 is about keeping the two apart.

6. zip(): Two Collections Side by Side

Results often arrive as parallel lists — one of names, one of outcomes, in corresponding order:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset", "burst"]
statuses = ["PASS", "FAIL", "PASS"]

for test, status in zip(tests, statuses):
    print(test, "->", status)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke -> PASS
reset -> FAIL
burst -> PASS

zip() takes one item from each collection per pass and hands them over together. No index is involved, which means no index can be wrong.

It takes as many collections as you like:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]
seeds = [4210, 4211]
statuses = ["PASS", "FAIL"]

for test, seed, status in zip(tests, seeds, statuses):
    print(test, seed, status)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke 4210 PASS
reset 4211 FAIL

And a zip of two is exactly the shape a dictionary wants:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]
statuses = ["PASS", "FAIL"]

print(dict(zip(tests, statuses)))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{'smoke': 'PASS', 'reset': 'FAIL'}

7. What zip() Hands You

A tuple per pass, one field per collection:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]
statuses = ["PASS", "FAIL"]

print(list(zip(tests, statuses)))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
[('smoke', 'PASS'), ('reset', 'FAIL')]

Which is why for test, status in zip(...) works: it is tuple unpacking, same as enumerate().

8. zip() Stops at the Shortest, Silently

This is the sharp edge, and it loses data without saying so:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
tests = ["smoke", "reset", "burst"]
statuses = ["PASS", "FAIL"]

for test, status in zip(tests, statuses):
    print(test, status)

print("paired:", len(list(zip(tests, statuses))), "of", len(tests))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke PASS
reset FAIL
paired: 2 of 3

Three tests, two statuses, two rows. "burst" is not in the output and nothing reported that it was dropped.

Consider what that means in a results pipeline. If the status list was built by a loop that skipped a crashed run, the lists are no longer parallel — and the report that zip() produces is not merely missing a row, it is wrong from the point of the mismatch onwards, because each remaining status is attached to the wrong test. A report that silently renames failures is worse than one that fails.

9. strict=True Turns It Into an Error

Since Python 3.10, zip() can insist that its arguments are the same length:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
tests = ["smoke", "reset", "burst"]
statuses = ["PASS", "FAIL"]

for test, status in zip(tests, statuses, strict=True):
    print(test, status)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke PASS
reset FAIL
ValueError: zip() argument 2 is shorter than argument 1

Two things worth noticing. The pairs that did match were produced before the error — zip() discovers the mismatch when it runs out, not in advance. And the message names which argument was short, which is the information you need.

Use strict=True whenever the collections are supposed to be the same length. That is almost always, and it converts a silent wrong answer into a loud stop. Leave it off only when truncation is genuinely what you want.

10. Both Together

Numbering paired rows needs both, and the unpacking needs brackets:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tests = ["smoke", "reset"]
statuses = ["PASS", "FAIL"]

for i, (test, status) in enumerate(zip(tests, statuses), start=1):
    print(i, test, status)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 smoke PASS
2 reset FAIL

enumerate() hands over a pair whose second field is itself a pair, so the names must be grouped the same way: i takes the number, and (test, status) takes the tuple. Without the brackets Python has three names for two values and refuses.

Beyond this depth, the line stops being readable and a small loop body with explicit unpacking is better.

11. Predict, Run, Observe

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# A
for i, ch in enumerate("abc"):
    print(i, ch)

# B
for i, name in enumerate(["x", "y"], start=10):
    print(i, name)

# C
a = [1, 2, 3]
b = ["one", "two"]
pairs = list(zip(a, b))
print("C:", pairs, len(pairs))

# D
keys = ["clk", "rst"]
vals = [1, 1]
print("D:", dict(zip(keys, vals)))

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
0 a
1 b
2 c
10 x
11 y
C: [(1, 'one'), (2, 'two')] 2
D: {'clk': 1, 'rst': 1}

A — a string is a collection, so enumerate() numbers its characters.

B — start sets where the numbering begins; it does not skip items.

C — two pairs from a three-item and a two-item list. The 3 is gone.

D — paired then built into a dictionary in one expression.

12. Exercises

Exercise 1 — number a report. Print 1. smoke, 2. reset, 3. burst from a list, using enumerate(). Then change it to print zero-based positions and say which you would show a user.

Exercise 2 — remove the index. Rewrite each without range(len(...)): (a) print each test with its position (b) print each line of a log prefixed with its line number starting at 1.

Exercise 3 — the misplaced increment. Run Section 5's broken program. Then move the increment out of the if and predict the new output before running. Explain in two sentences what each version is counting.

Exercise 4 — pair up. Given names = ["clk", "rst", "data"] and widths = [1, 1, 32], print clk is 1 bit(s) for each. Then build a dictionary from the two lists in one line.

Exercise 5 — find the dropped item. Given a three-item and a two-item list, use zip() and print how many pairs resulted. Name the item that was lost and explain how you would detect this in a program rather than by eye.

Exercise 6 — make it loud. Repeat Exercise 5 with strict=True, record the exact message, and say what the message tells you that the silent version did not.

Exercise 7 — three at once. Given test names, seeds and statuses as three parallel lists of equal length, print one line per run containing all three. Add strict=True and say why it costs nothing here.

Exercise 8 — combine. Print 1. smoke -> PASS and 2. reset -> FAIL from two parallel lists using enumerate() and zip() together. Then write the same thing as a loop over a list of tuples and say which you would rather maintain.

13. Summary

  • enumerate() supplies a running number alongside each item, with no counter to create or advance.
  • start=1 for numbers shown to people, the default zero for positions used as indices.
  • It hands you a two-field tuple per pass, unpacked by the two names before in.
  • It replaces for i in range(len(x)): — no lookup, no out-of-range, works on any collection.
  • A hand-maintained counter can be advanced in the wrong place and will then count matches instead of positions, silently. enumerate() makes that impossible.
  • zip() walks two or more collections together, handing over one item from each per pass.
  • zip() stops at the shortest argument and says nothing — three tests and two statuses give two rows, and the third test vanishes.
  • A mismatch does not just drop a row; it misaligns every pair after it, which can attach the wrong status to the wrong test.
  • strict=True turns that into a ValueError naming the short argument. Use it whenever the lists are supposed to match.
  • Combining them needs grouped names: for i, (a, b) in enumerate(zip(x, y)):.

The next chapter is about the other thing a loop body does with a value it computes: keeping it.

Where this fits

Part of the Python curriculum.