Doing Something More Than Once
Looping in Python means iterating over the thing itself, not counting up to its length and hoping you stop in time. Step through a collection one item at a time and watch the loop variable outlive the loop that created it.
Looping in Python means iterating over the thing itself, not counting up to its length and hoping you stop in time. Step through a collection one item at a time and watch the loop variable outlive the loop that created it.
Iterating over the thing, not an index into it
Many languages loop by counting: start at 0, stop before the length, index in each time. Python's for hands you the items directly.
$ >>> for fruit in ["apple", "banana", "cherry"]:$ ... print(fruit)$ apple$ banana$ cherryfruit is not an index — it is each item, in turn. That is the idiom: if you catch yourself writing for i in range(len(fruits)): and then immediately indexing fruits[i], you have written the position-counting version of a loop that Python already gives you directly.
If you need the position too, enumerate() hands you both without you tracking a counter yourself.
$ >>> for i, fruit in enumerate(["apple", "banana", "cherry"]):$ ... print(i, fruit)$ 0 apple$ 1 banana$ 2 cherryrange is a sequence you can count through, not a list
range(5) looks like it should produce a list of five numbers, and printing the result of looping over it certainly behaves that way. It is not one — it is a lazy sequence that produces each number only as the loop asks for it.
$ >>> range(5)$ range(0, 5)$ >>> list(range(5))$ [0, 1, 2, 3, 4]$ >>> list(range(2, 10, 3))$ [2, 5, 8]range(5) counts from 0 up to, but never including, 5 — five numbers, not six. range(2, 10, 3) adds a start and a step: begin at 2, add 3 each time, stop before 10. The stop value is never itself produced, which is the same off-by-one convention string slicing uses.
zip walks two collections side by side
Looping over two lists in step usually starts as indexing both of them by the same counter. zip() pairs them up directly, one item from each per iteration.
$ >>> names = ["Ada", "Grace", "Alan"]$ >>> scores = [98, 91, 87]$ >>> for name, score in zip(names, scores):$ ... print(f"{name}: {score}")$ Ada: 98$ Grace: 91$ Alan: 87If the two collections are different lengths, zip() stops as soon as the shorter one runs out, silently. A fourth name with no matching score never produces a pairing and never raises an error. Check this explicitly if mismatched lengths would actually be a bug in your program rather than expected.
Current iteration: number = 12
total = 0
While: repeating until something changes
Use for when you already have the collection to iterate over. Use while when you are repeating until a condition changes, and you do not know in advance how many times that will take.
$ >>> total = 0$ >>> while total < 10:$ ... total += 3$ >>> total$ 12break, continue, and what else means on a loop
Two keywords change a loop's path through its own body without changing the condition that controls it. break exits the loop immediately, skipping every remaining iteration. continue skips only the rest of the current iteration and moves on to the next one.
- 1
break stops the loop outright
Found what you were looking for? Stop checking the rest — nothing after break in that iteration runs, and no further iterations happen either.
- 2
continue skips to the next iteration
Want to ignore this one item and move on? continue jumps straight back to the top of the loop for the next value, without exiting.
- 3
else runs only if the loop was never broken
A for or while loop can carry its own else block, which runs when the loop finishes normally — and is skipped entirely if break ever fired.
$ >>> for n in [2, 4, 6, 9, 10]:$ ... if n % 2 != 0:$ ... print(f"found an odd one: {n}")$ ... break$ ... else:$ ... print("every number was even")$ found an odd one: 9Change the list to all-even numbers and the loop finishes without ever hitting break, so the else block runs and prints "every number was even". This is the one place else does not mean "otherwise" the way it does on an if — here it means "the loop completed without a break", which is precisely the condition you would otherwise track with a separate flag variable.
The loop variable is still there after the loop ends
Unlike some languages, Python does not create a fresh scope for a loop body. The variable you loop with is an ordinary name in the surrounding function or module, and it keeps whatever value it last held once the loop finishes.
$ >>> for fruit in ["apple", "banana", "cherry"]:$ ... pass$ >>> fruit$ 'cherry'$ >>> numbers = [2, 4, 6, 7, 8]$ >>> for n in numbers:$ ... if n % 2 == 0:$ ... numbers.remove(n)$ >>> numbers$ [4, 7]4 survives, and it is even — it never gets checked. Removing 2 shifts 4 down into the slot the loop has already passed, so the loop's cursor jumps straight over it to 6. Loop over numbers[:], a copy, or build a new list with a comprehension instead, and the loop you are iterating never changes shape underneath you.
Key takeaways
- for iterates over the items directly. If you are indexing with a counter, you have written the version Python already does for you.
- range is lazy: range(1_000_000_000) holds three numbers, not a billion, and produces each value only when the loop asks.
- zip pairs two collections item by item and stops silently at the shorter one — check lengths yourself if a mismatch would be a bug.
- break exits a loop outright; continue skips to the next iteration; a loop's own else runs only if break never fired.
- Never add to or remove from a list while a for loop is iterating over it — items shift underneath the loop and get silently skipped.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.