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
fruits = ["apple", "banana", "cherry"] for fruit in fruits: print(fruit.title()) for letter in "hey": print(letter)
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:
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)
[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
podium = ["Mia", "Leo", "Ava"] for place, name in enumerate(podium, start=1): print(f"{place}. {name}")
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
names = ["Ana", "Ben", "Cleo"] scores = [91, 78, 85] for name, score in zip(names, scores): print(f"{name}: {score}") print(dict(zip(names, scores)))
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
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")
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
nums = [3, 1, 2] for n in reversed(nums): print(n) print(*sorted(nums)) print(*sorted(["pear", "Apple", "fig"], key=str.lower))
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
for row in range(1, 4): line = "" for col in range(1, 4): line += f"{row * col:4}" print(line)
1 2 3 2 4 6 3 6 9
Don't modify a list while looping over it
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)
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:
for i in range(3): pass print(i) for _ in range(2): print("hello")
2 hello hello