Skip to main content

Looping like a Pythonista: enumerate, zip & items

Level 1: Level 1: Foundationseasy10 minenumeratezipdict-itemsiteration

Loop with a counter (enumerate), over two lists at once (zip), and over a dict (.items()).

Loop over what you have, not over indexes

Reaching for range(len(items)) and indexing back with items[i] is the beginner tell. It reads noisily, breaks the moment you rename or reorder things, and is the classic home of off-by-one bugs. Python hands you iterators that give you exactly what you need, so you loop over the data itself instead of bookkeeping positions.

Table
Every left-hand form works and every one re-derives something Python already had. zip is also the safer choice for the third row: it stops at the SHORTER sequence instead of raising IndexError when the lengths differ.
Instead ofWriteEach turn gives you
for i in range(len(xs)): xs[i]for x in xsthe value
for i in range(len(xs)): i, xs[i]for i, x in enumerate(xs)the position and the value
for i in range(len(a)): a[i], b[i]for x, y in zip(a, b)one item from each, in lockstep
for k in d: k, d[k]for k, v in d.items()the key and its value
Every left-hand form works and every one re-derives something Python already had. zip is also the safer choice for the third row: it stops at the SHORTER sequence instead of raising IndexError when the lengths differ.

enumerate: the value plus its position

enumerate(iterable) wraps any iterable and yields (index, value) pairs, lazily, one at a time.

for i, letter in enumerate(["a", "b", "c"]):
    print(i, letter)
# 0 a
# 1 b
# 2 c

Counting starts at 0. Need 1-based numbering (line numbers, ranks)? Pass start: enumerate(items, start=1). Do not hand-roll i + 1, and do not fall back to range. Each pair is a tuple, so i, letter unpacks it. When you actually need a [index, value] list (the Apply asks for exactly this), build one per item: [i, value].

zip: walk several sequences in lockstep

zip(a, b) pairs items by position: first of a with first of b, second with second, and so on. It is how you iterate two parallel lists without indexing either.

for name, score in zip(["Ada", "Sam"], [90, 85]):
    print(name, score)
# Ada 90
# Sam 85
Check yourself
A nightly report zips 300 names with 300 scores. One night the scores file arrives with only 299 rows. What does the report look like?

To collect [name, score] lists (the Practice), build [name, score] inside the loop or a comprehension.

.items(): keys and values from a dict together

Iterating a dict directly gives only keys. .items() gives both:

prices = {"apple": 3, "pear": 2}
for fruit, price in prices.items():
    print(fruit, price)
# apple 3
# pear 2

.keys() and .values() give one side each. Since Python 3.7, all three iterate in insertion order.

Pitfalls

  • zip silently truncates to the shortest input. zip(["a", "b", "c"], [1, 2]) yields only two pairs and drops "c" with no error. If your lists are meant to be the same length, that hides a data bug. Fix: assert len(a) == len(b) first, or use zip(a, b, strict=True) (Python 3.10+), which raises ValueError on a length mismatch.
Check yourself
What does list(enumerate(['a'])) give you?
  • enumerate yields tuples, not lists. list(enumerate(["a"])) is [(0, "a")]. If the caller expects [0, "a"], convert explicitly.
Check yourself
z = zip(names, scores). You call list(z), get your pairs, then call list(z) again. What does the second call return?

Interview nuance: both enumerate and zip return lazy iterators in Python 3, not lists. They pull one item at a time and use O(1) extra memory regardless of input size, which is why they scale to large or streaming data. The catch is single-pass: an iterator is exhausted after one loop. z = zip(a, b); list(z) gives the pairs, but a second list(z) gives [], because the first pass consumed it. Wrap in list(...) once if you need to iterate the result more than once.

Worked example (Python)
for i, letter in enumerate(["a", "b", "c"]):
    print(i, letter)

for name, score in zip(["Ada", "Sam"], [90, 85]):
    print(name, score)

Apply

Your turn

The task this lesson builds to.

Implement indexed(items): return a list of [index, value] pairs, numbering each item from 0. Use enumerate.

For ["a", "b"] return [[0, "a"], [1, "b"]].

2 hints and 3 automated checks are waiting in the workspace.

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Implement pair_totals(names, scores): return a list of [name, score] pairs by walking both lists together with zip.

(["a", "b"], [1, 2]) returns [["a", 1], ["b", 2]].

2 hints and 3 automated checks are waiting in the workspace.