Loops in Python: while and for
Repeating work with while and for loops: range(), accumulator patterns, break and continue, the loop else clause, nested loops, and enumerate() and zip().
Repeating while a condition holds
A loop runs a block of code repeatedly. The while loop is the simplest: it checks its condition, runs the block if the condition is true, then goes back and checks again. When the condition becomes false, the program continues after the loop.
Something inside the block must eventually make the condition false. If nothing does, the loop never ends, an infinite loop. (In a terminal, Ctrl+C stops a runaway program.)
Use while when you do not know in advance how many repetitions you need: keep asking until the input is valid, keep halving until the number reaches 1, keep reading until the file ends.
1savings = 10002years = 03while savings < 2000:4 savings = savings * 1.075 years += 16print(f"Doubled after {years} years: {savings:.2f}")78countdown = 39while countdown > 0:10 print(countdown)11 countdown -= 112print("Go!")
Doubled after 11 years: 2104.85 3 2 1 Go!
Visiting every item with for
The for loop walks through a sequence (or any other iterable) and runs its block once per item, with the loop variable bound to the current item. It stops by itself after the last item, so there is no condition to get wrong.
Strings, lists, tuples, dictionaries, sets, files and range objects are all iterable. Whenever you think "for each item in ...", write a for loop.
1languages = ["Python", "SQL", "JavaScript"]2for language in languages:3 print("I am learning", language)45for letter in "abc":6 print(letter.upper(), end=" ")7print()
I am learning Python I am learning SQL I am learning JavaScript A B C
Counting with range()
range() produces a sequence of integers without building a list in memory. It takes up to three arguments, just like slicing:
range(stop): 0, 1, ..., stop - 1range(start, stop): start, start + 1, ..., stop - 1range(start, stop, step): counts bystep, which may be negative
The stop value is never included, so range(5) runs exactly five times and range(1, n + 1) covers 1 to n. list(range(...)) shows the numbers when you want to see them.
1print(list(range(5)))2print(list(range(2, 7)))3print(list(range(0, 20, 5)))4print(list(range(10, 0, -3)))5for i in range(3):6 print("Attempt", i + 1)
[0, 1, 2, 3, 4] [2, 3, 4, 5, 6] [0, 5, 10, 15] [10, 7, 4, 1] Attempt 1 Attempt 2 Attempt 3
Accumulator patterns
Most loops build up a result step by step. The pattern always has three parts: initialise a variable before the loop, update it inside the loop, use it after the loop.
- Sum: start at 0, add each item.
- Count: start at 0, add 1 when an item matches.
- Maximum / minimum: start with the first item (or
None), replace it when you find a better one. - Collect: start with an empty list or string, append what you need.
Python has built-ins for the simplest cases (sum, len, max, min), but the manual pattern is what you use whenever the rule is your own.
1temperatures = [21, 25, 19, 30, 27, 18]2total = 03hot_days = 04warmest = temperatures[0]5above_20 = []6for t in temperatures:7 total += t8 if t > 25:9 hot_days += 110 if t > warmest:11 warmest = t12 if t > 20:13 above_20.append(t)14print(f"Average: {total / len(temperatures):.1f}")15print("Hot days:", hot_days, "Warmest:", warmest)16print("Above 20:", above_20)
Average: 23.3 Hot days: 2 Warmest: 30 Above 20: [21, 25, 30, 27]
Tracing a loop
Follow the loop below one step at a time. Notice that the for line runs once per item (each time it binds the next value of n), and that the if body only runs for even numbers.
Adding the even numbers from 1 to 5
total = 0for n in range(1, 6): if n % 2 == 0: total += nprint(total)| Step | n | total | State Evolution |
|---|---|---|---|
| 1 | — | 0 | The accumulator total starts at 0. |
break and continue
Two statements change a loop's flow from inside:
breakleaves the loop immediately; the program continues after it. Use it to stop searching once you have found what you need.continueskips the rest of the current iteration and moves on to the next item. Use it to skip items that do not apply, which keeps the main logic unindented.
A while True: loop with a break inside is a common, deliberate pattern when the stopping condition is easiest to check in the middle of the loop.
1orders = [120, -5, 80, 0, 300, 45]2for amount in orders:3 if amount <= 0:4 continue5 if amount > 250:6 print("Large order found:", amount)7 break8 print("Processing", amount)910n = 111while True:12 n *= 313 if n > 100:14 break15print("First power of 3 above 100:", n)
Processing 120 Processing 80 Large order found: 300 First power of 3 above 100: 243
The loop else clause
A for or while loop may have an else block. It runs when the loop finishes normally, that is, without hitting break. That makes it a clean way to express "searched everything and did not find it" without a separate flag variable.
Read the loop else as "no break". If that reading would confuse your team, a boolean flag such as found = False is a perfectly good alternative.
1for candidate in [21, 23]:2 for d in range(2, candidate):3 if candidate % d == 0:4 print(candidate, "is divisible by", d)5 break6 else:7 print(candidate, "is prime")
21 is divisible by 3 23 is prime
Nested loops
A loop inside a loop runs the inner loop completely for each iteration of the outer one. Nested loops are natural for grids, tables and comparing every item with every other item.
The work multiplies: an outer loop of 100 and an inner loop of 100 run the inner block 10,000 times. That is fine for small data, and the reason to look for a smarter approach when inputs get large.
1for row in range(1, 4):2 line = ""3 for col in range(1, 5):4 line += f"{row * col:4}"5 print(line)67size = 48for i in range(1, size + 1):9 print("*" * i)
1 2 3 4 2 4 6 8 3 6 9 12 * ** *** ****
enumerate() and zip()
Two built-ins remove most of the need for index arithmetic:
enumerate(items)gives(index, item)pairs, so you get the position withoutrange(len(items)). Passstart=1for human-friendly numbering.zip(a, b)walks two (or more) sequences in step, giving one item from each per iteration. It stops at the end of the shortest sequence.
Both work with tuple unpacking in the for line: for i, name in enumerate(names):.
1names = ["Ada", "Grace", "Linus"]2scores = [91, 88, 79]3for position, name in enumerate(names, start=1):4 print(position, name)5for name, score in zip(names, scores):6 print(f"{name:<6}{score:>4}")
1 Ada 2 Grace 3 Linus Ada 91 Grace 88 Linus 79
Choosing the right loop
A simple rule covers nearly every case: if you are processing each item of a collection or repeating a known number of times, use for. If you are repeating until something happens and cannot predict when, use while.
- ✓Iterate directly over items: for name in names.
- ✓Use enumerate when you also need the index.
- ✓Make sure every while loop changes something its condition depends on.
- ✓Give the loop variable a singular name of what it holds: for order in orders.
- ✕Write for i in range(len(items)) just to read items[i].
- ✕Add or remove items of a list while looping over that same list.
- ✕Use while with a manual counter where for ... in range() says it directly.
- •for visits each item of an iterable; while repeats until its condition is false.
- •range(start, stop, step) never includes stop.
- •Initialise accumulators before the loop and use them after it.
- •break stops a loop, continue skips to the next iteration, and the loop's else runs only if there was no break.
Check your understanding
How many times does `for i in range(2, 10, 3):` run its body?
Practice
Loop practice in rising difficulty: digit sums with while, the Collatz sequence, FizzBuzz with a prime twist, nested-loop patterns, and run-length encoding.
Sum of Digits
Reveal Solution & AnalysisHide Solution
Collatz Steps
Reveal Solution & AnalysisHide Solution
FizzBuzz with a Twist
Reveal Solution & AnalysisHide Solution
Pattern Printing with Nested Loops
Reveal Solution & AnalysisHide Solution
Run-Length Encoding
Reveal Solution & AnalysisHide Solution
Quick Syntax & Reference Sheet
| Concept / Rule | Syntax / Usage |
|---|---|
| Loop over items | for item in items: |
| Loop n times | for i in range(n): |
| Index and item | for i, item in enumerate(items, start=1): |
| Two lists in step | for a, b in zip(xs, ys): |
| Repeat until done | while not done: |
| Stop / skip | break / continue |
| Searched and not found | for ...: ... break
else: not_found() |
Common Pitfalls & Developer Mistakes
Off-by-one with range
Why it happens: range(1, 10) stops at 9.
A while loop that never ends
Why it happens: Nothing in the body changes the variables in the condition.
Resetting the accumulator inside the loop
Why it happens: total = 0 inside the loop throws away the previous iterations each time.
Modifying a list while iterating over it
Why it happens: Removing items shifts the remaining ones, so some are skipped.