Python enumerate & zip
Beginner Control Flow

Python enumerate() and zip()

1Python enumerate & zip: overview

enumerate() gives you each item together with its position, and zip() walks two or more sequences side by side, so one for loop can handle matching items without any index juggling.

GoalBy the end you can number items with enumerate(), start counting at 1, walk several lists in step with zip(), build a dictionary with dict(zip(keys, values)), and explain why both return one-use lazy iterators.

Before this lesson: Python for Loops, Python range() Function

2Python enumerate & zip: diagram

enumerate(names, start=1) + zip(names, scores)enumerate(names, start=1) + zip(names, scores)numbernamescore1Asha882Ben923Chen79enumerate supplies the number column; zip lines up each name with the score at the same position.
Picture three columns. enumerate() adds the numbering column, and zip() lines up the names with the scores, row by row. Every pass of the loop receives one whole row, which you unpack into variables such as number, name or name, score.

3Python enumerate & zip: explained step by step

A plain for loop hands you the items of a collection, but it doesn't tell you where an item came from, and it can only walk one collection at a time. Two built-in helpers fix that: enumerate() adds the position, and zip() walks several collections in step. (A list is just an ordered collection written in square brackets, like ["Asha", "Ben"]. That is all you need to know about lists here.)

enumerate(): the item and its position

enumerate(items) produces pairs of (index, item): first (0, first item), then (1, second item), and so on.

Python
names = ["Asha", "Ben", "Chen"]
for index, name in enumerate(names):
    print(index, name)

The loop header has two variable names. Each pair coming out of enumerate is unpacked into them by position: the first value goes into index, the second into name. With only one variable, the loop would hold the whole pair instead:

Python
for pair in enumerate(["Asha", "Ben"]):
    print(pair)

Counting from 1 with start

Indexes begin at 0, but people count from 1. Pass start=1 and the numbering begins there. Only the numbers change: you still get every item, and none are skipped.

Python
names = ["Asha", "Ben", "Chen"]
for number, name in enumerate(names, start=1):
    print(f"{number}. {name}")

Why not range(len(items))?

You can get the same result with the older style:

for i in range(len(names)):
    print(i, names[i])

It works, but you must build a range, ask for the length, and look every item up by index yourself. That is three chances to slip: range(1, len(names)) silently skips the first item, and names[i + 1] crashes on the last one. enumerate says what you mean ("each item with its position") in one step, and it also works on things you can loop over but cannot index, such as the lines of a file.

zip(): walk through lists in step

Sometimes two lists belong together: names and their scores, products and their prices. zip(names, scores) pairs the first name with the first score, the second with the second, and so on, like the two sides of a zip fastener meeting.

Python
names = ["Asha", "Ben", "Chen"]
scores = [88, 92, 79]
for name, score in zip(names, scores):
    print(name, "scored", score)

You are not limited to two. Give zip three lists and every item is a triple:

Python
names = ["Asha", "Ben", "Chen"]
scores = [88, 92, 79]
grades = ["B", "A", "C"]
for name, score, grade in zip(names, scores, grades):
    print(name, score, grade)

zip stops at the shortest

If the lists have different lengths, zip stops as soon as the shortest one runs out. The leftover items are dropped without any warning.

Python
days = ["Mon", "Tue", "Wed", "Thu"]
sales = [120, 95, 140]
for day, amount in zip(days, sales):
    print(day, amount)
print("Pairs made:", len(list(zip(days, sales))))

Thu has no sales figure, so it simply vanishes. If a length mismatch would mean your data is broken, add strict=True (Python 3.10 and newer). Then a mismatch raises a ValueError instead of quietly losing data:

for day, amount in zip(days, sales, strict=True):
    print(day, amount)
# Mon, Tue and Wed are printed, then Python stops with:
# ValueError: zip() argument 2 is shorter than argument 1

Using both together

To number the pairs that zip produces, wrap it in enumerate. Each item is now (number, (name, score)), so the inner pair gets its own parentheses when you unpack it:

Python
names = ["Asha", "Ben", "Chen"]
scores = [88, 92, 79]
for number, (name, score) in enumerate(zip(names, scores), start=1):
    print(f"{number}. {name} - {score}")

Building a dictionary from two lists

A dictionary stores key-value pairs, and zip(keys, values) produces exactly such pairs, so dict() can swallow it in one go:

Python
keys = ["name", "age", "city"]
values = ["Asha", 21, "Pune"]
profile = dict(zip(keys, values))
print(profile)
print(profile["city"])

Unzipping with zip(*pairs)

The same tool works in reverse. The * spreads a list of pairs into separate arguments, and zip then regroups all the first items together and all the second items together:

Python
pairs = [("Asha", 88), ("Ben", 92), ("Chen", 79)]
names, scores = zip(*pairs)
print(names)
print(scores)

The results are tuples (read-only lists written with parentheses).

They are lazy: one pass only

Neither enumerate() nor zip() builds its pairs up front. They return an iterator, a small object that makes the next pair only when something asks for it. If you write print(enumerate(names)) you will see text like <enumerate object at 0x7f3c...> (the address changes from run to run) instead of the pairs. A for loop asks for one pair at a time, so loops work as usual. To look at all the pairs at once, wrap the iterator in list().

The catch: an iterator can be used once. After a loop, or a list(), has taken all of its pairs, it is empty.

Python
names = ["Asha", "Ben"]
pairs = enumerate(names, start=1)
print(list(pairs))
print(list(pairs))

The second line shows an empty list: the first list(pairs) already used everything up. zip behaves the same way. If you need the pairs again, build a fresh zip(...) or keep them with pairs = list(zip(...)).

Quick reference: Python enumerate & zip

4Python enumerate & zip: syntax

enumerate(iterable)                # (0, item), (1, item), ...
enumerate(iterable, start=1)       # (1, item), (2, item), ...
for index, item in enumerate(items):
    ...

zip(a, b)                          # (a0, b0), (a1, b1), ... stops at the shortest
zip(a, b, c)                       # three items at a time
zip(a, b, strict=True)             # ValueError if the lengths differ (3.10+)
for x, y in zip(a, b):
    ...

dict(zip(keys, values))            # build a dictionary
firsts, seconds = zip(*pairs)      # unzip a list of pairs
list(enumerate(items))             # see all the pairs at once

5Python enumerate & zip: run the example

Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.

Interactive example
students = ["Asha", "Ben", "Chen", "Dia"]
scores = [88, 92, 79, 95]

for number, name in enumerate(students, start=1):
    print(f"{number}. {name}")

for name, score in zip(students, scores):
    if score >= 90:
        print(name, "earned an A with", score)

report = dict(zip(students, scores))
print(report)
Expected output1. Asha
2. Ben
3. Chen
4. Dia
Ben earned an A with 92
Dia earned an A with 95
{'Asha': 88, 'Ben': 92, 'Chen': 79, 'Dia': 95}

7Python enumerate & zip: how the example works

Hover or tap a step to highlight its lines in the example above.

  1. Line 1-2Two lists of the same length. The item at position 0 of students belongs with the item at position 0 of scores, and so on.
  2. Line 4-5enumerate(students, start=1) produces (1, "Asha"), (2, "Ben"), ... Each pair is unpacked into number and name, which gives a numbered list.
  3. Line 7zip(students, scores) pairs each student with the score at the same position: ("Asha", 88), ("Ben", 92), ... Each pair is unpacked into name and score.
  4. Line 8-9Only scores of 90 or more get a message, so just Ben and Dia are printed. No index was needed to match a name with its score.
  5. Line 11A fresh zip (the one in the loop above is already used up) feeds its pairs to dict(), which turns each name into a key and each score into its value.
  6. Line 12The dictionary is printed in the order the pairs were added.

8Python enumerate & zip: common mistakes

Swapping the names in the unpacking

✗ Wrong

names = ["Asha", "Ben"]
for name, index in enumerate(names):
    print(f"{name} is at {index}")
Prints "0 is at Asha" and "1 is at Ben": the labels are the wrong way round

✓ Right

names = ["Asha", "Ben"]
for index, name in enumerate(names):
    print(f"{name} is at {index}")

enumerate always puts the index first and the item second, and unpacking assigns by position, not by name. So name received the number. Write for index, name in enumerate(names).

Forgetting to unpack the pair

✗ Wrong

names = ["Asha", "Ben"]
for item in enumerate(names):
    print(item.upper())
AttributeError: 'tuple' object has no attribute 'upper'

✓ Right

names = ["Asha", "Ben"]
for index, name in enumerate(names):
    print(name.upper())

With a single loop variable, item receives the whole (index, name) pair, which is a tuple and not a string. Unpack the pair in the for line, or pick the name with item[1].

Using a zip twice

✗ Wrong

names = ["Asha", "Ben"]
scores = [88, 92]
pairs = zip(names, scores)
print(list(pairs))
for name, score in pairs:
    print(name, score)
The list prints fine, but the loop prints nothing at all: the zip was used up by list()

✓ Right

names = ["Asha", "Ben"]
scores = [88, 92]
pairs = list(zip(names, scores))
print(pairs)
for name, score in pairs:
    print(name, score)

A zip is a one-use iterator. Once list(pairs) has taken every pair, nothing is left for the loop. Store the pairs in a list if you need them more than once, or build a fresh zip(...) each time.

Losing items when the lists have different lengths

✗ Wrong

names = ["Asha", "Ben", "Chen"]
scores = [88, 92]
for name, score in zip(names, scores):
    print(name, score)
Prints only Asha and Ben. Chen quietly disappears and Python raises no error

✓ Right

names = ["Asha", "Ben", "Chen"]
scores = [88, 92, 79]
for name, score in zip(names, scores, strict=True):
    print(name, score)

zip stops at the shortest list and drops the rest without a warning. Fix the data, and if the lengths are supposed to match, add strict=True so any future mismatch raises a ValueError instead (Python 3.10+).

9Python enumerate & zip: exercises

Write your answer, then press Check. The checks run in your browser.

1. Number the playlist

Write code Easy

Use enumerate() to print the songs as a numbered list that starts at 1, using the loop variables number and song. The output must be:

1. Blue Sky
2. Night Drive
3. Paper Boats
4. Slow River
Your code
songs = ["Blue Sky", "Night Drive", "Paper Boats", "Slow River"]

# print each song with its number, starting at 1

2. Predict enumerate around zip

Predict the output Medium

Read the code and type exactly what it will print. Then press Check to run it and compare.

Read this code
colors = ["red", "green", "blue"]
codes = [1, 2]
for i, (c, n) in enumerate(zip(colors, codes), start=10):
    print(i, c, n)
print(list(zip(colors, codes)))

2 more exercises → 6 more examples

Finished this lesson?
Progress is saved in this browser — create a free account to keep it across devices.

Questions learners ask

What is the difference between enumerate() and range(len())?

Both can give you indexes, but enumerate() hands you the index and the item together, so you never have to write items[i]. It reads better, avoids off-by-one slips, and works on anything you can loop over, not only lists.

What happens if the lists given to zip() have different lengths?

zip() stops as soon as the shortest list is used up and ignores the extra items, with no error. In Python 3.10 and newer, zip(a, b, strict=True) raises a ValueError when the lengths differ. If you want the longer list kept and the shorter one padded, look at itertools.zip_longest().

Why does printing a zip or enumerate show something like <zip object at 0x...>?

Both return lazy iterators that create pairs only when asked, so printing one shows the object, not its contents. Wrap it in list() to see the pairs: print(list(zip(a, b))). An iterator can be used only once: after a loop or a list() call has taken the pairs, it is empty.

Further reading

10Next lesson

Up nextPython ListsA list is an ordered, changeable collection that stores many values under one name, each at a numbered position called …

Related:
Python for LoopsPython range() FunctionPython DictionariesPython List Comprehensions

← Python Nested Loops · All Control Flow lessons · Practise in the IDE