for Loops & range()

Python's for loop visits each item of a sequence — a list, a string, a dictionary, a file — one by one. Combined with range() for counting, enumerate() for positions and zip() for walking several sequences together, it handles nearly every looping job.

Looping over a sequence

PY for.py
fruits = ["apple", "banana", "cherry"]

for fruit in fruits:
    print(fruit.title())

for letter in "hey":
    print(letter)
Output
Apple
Banana
Cherry
h
e
y

Each pass assigns the next item to the loop variable. There's no index to manage and no way to go past the end.

range()

range produces a sequence of integers on demand:

PY range.py
print(list(range(5)))
print(list(range(2, 7)))
print(list(range(0, 20, 5)))
print(list(range(5, 0, -1)))

for i in range(3):
    print("Lap", i + 1)
Output
[0, 1, 2, 3, 4]
[2, 3, 4, 5, 6]
[0, 5, 10, 15]
[5, 4, 3, 2, 1]
Lap 1
Lap 2
Lap 3

range(stop), range(start, stop) and range(start, stop, step) — the stop value is never included. A range doesn't create a list in memory; it generates numbers as needed, so range(1_000_000_000) is fine.

enumerate(): index and item together

PY enumerate.py
podium = ["Mia", "Leo", "Ava"]

for place, name in enumerate(podium, start=1):
    print(f"{place}. {name}")
Output
1. Mia
2. Leo
3. Ava

Whenever you're tempted to write for i in range(len(items)):, use enumerate instead — it's clearer and less error-prone.

zip(): several sequences in parallel

PY zip.py
names = ["Ana", "Ben", "Cleo"]
scores = [91, 78, 85]

for name, score in zip(names, scores):
    print(f"{name}: {score}")

print(dict(zip(names, scores)))
Output
Ana: 91
Ben: 78
Cleo: 85
{'Ana': 91, 'Ben': 78, 'Cleo': 85}

zip stops at the shortest input. Pass strict=True to raise an error if the lengths differ — a good safety check.

Looping over dictionaries

PY dict_loop.py
stock = {"pens": 12, "pads": 0, "clips": 40}

for item in stock:
    print(item)

for item, count in stock.items():
    if count == 0:
        print(f"{item} is out of stock")
Output
pens
pads
clips
pads is out of stock

Looping over a dictionary gives its keys. Use .items() for key–value pairs and .values() for just the values (lesson 24).

Useful helpers: reversed and sorted

PY helpers.py
nums = [3, 1, 2]

for n in reversed(nums):
    print(n)

print(*sorted(nums))
print(*sorted(["pear", "Apple", "fig"], key=str.lower))
Output
2
1
3
1 2 3
Apple fig pear

reversed and sorted loop in a different order without changing the original list. The * in print(*items) unpacks a sequence into separate arguments, so they're printed separated by spaces.

Nested loops

PY nested.py
for row in range(1, 4):
    line = ""
    for col in range(1, 4):
        line += f"{row * col:4}"
    print(line)
Output
   1   2   3
   2   4   6
   3   6   9

Don't modify a list while looping over it

PY modify.py
numbers = [1, 2, 2, 3, 2]

for n in numbers:
    if n == 2:
        numbers.remove(n)
print("modified while looping:", numbers)

numbers = [1, 2, 2, 3, 2]
numbers = [n for n in numbers if n != 2]
print("built a new list:", numbers)
Output
modified while looping: [1, 3, 2]
built a new list: [1, 3]

Removing items shifts the remaining ones, so the loop skips some. Build a new list instead (a list comprehension, lesson 21), or loop over a copy: for n in numbers[:]:.

The loop variable after the loop

Unlike in many languages, the loop variable still exists after the loop, holding the last value. And _ is the conventional name for a variable you don't need:

PY after.py
for i in range(3):
    pass
print(i)

for _ in range(2):
    print("hello")
Output
2
hello
hello

Try it yourself

Try it yourself
Console output