PythonMastery

Loop with enumerate and zip, not range(len())

enumerate gives the position and the item together, start=1 numbers from one, and zip(strict=True) stops unequal lists silently dropping data.

for i in range(len(items)) is the loop people bring from other languages. Python has two built-ins that say what you actually mean, and one flag worth knowing about.

Before

python
runners = ["Ada", "Linus", "Grace"]

for i in range(len(runners)):
    print(i + 1, runners[i])
output
1 Ada
2 Linus
3 Grace

After

python
runners = ["Ada", "Linus", "Grace"]

for place, name in enumerate(runners, start=1):
    print(place, name)
output
1 Ada
2 Linus
3 Grace

No + 1, no indexing, and the names say what each value is.

Two lists side by side: zip, and its trap

zip walks two lists together. By default it stops at the shorter one, without a word:

python
names = ["Ada", "Linus", "Grace"]
times = [41.2, 43.9]

for name, t in zip(names, times):
    print(name, t)
output
Ada 41.2
Linus 43.9

Grace has vanished. Since Python 3.10, strict=True turns that into an error you can't miss:

python
names = ["Ada", "Linus", "Grace"]
times = [41.2, 43.9]

try:
    for name, t in zip(names, times, strict=True):
        print(name, t)
except ValueError as e:
    print("ValueError:", e)
output
Ada 41.2
Linus 43.9
ValueError: zip() argument 2 is shorter than argument 1

Why it works

enumerate wraps any iterable and hands back (position, item) pairs; zip hands back one item from each iterable at a time. Neither needs the data to support indexing, so both work on files, generators and sets, where range(len()) can't.

When not to use it

If you need to change a list in place by position (prices[i] *= 1.1), enumerate still helps, but a comprehension that builds a new list is usually clearer. And use strict=True whenever the lists are meant to match; the default truncation is only right when you really do want "as many as the shorter one has".

Learn it properly: Loops: for, while, and the Pythonic Patterns, Lists & Sequences