Loops: for, while, and the Pythonic Patterns
1 · The lesson
readComputers are most useful when they do the same thing thousands of times: check every row in a file, every pixel in an image, every user in a database. A loop is how you tell Python to repeat work. Python has two kinds of loop, for and while, and a few helper functions (enumerate, zip, range). Experienced Python programmers use these helpers all the time.
1. The for Loop
A for loop goes through the items of any iterable, one at a time. An iterable is anything that can give you its items one after another: a list, a string, a dictionary, a range, and more.
fruits = ["apple", "banana", "cherry"] for fruit in fruits: print(fruit) # Strings are iterables of characters for char in "hello": print(char) # Dicts iterate over keys by default prices = {"apple": 1.20, "banana": 0.40} for key in prices: print(key, prices[key])
The loop variable (fruit, char, key) is a normal variable, so give it a meaningful name. After the loop ends, it keeps its last value.
2. range(): Counting
range produces integers on demand without building a list in memory.
for i in range(5): # 0, 1, 2, 3, 4 print(i) for i in range(2, 7): # 2, 3, 4, 5, 6 (the stop value is not included) print(i) for i in range(0, 20, 5): # 0, 5, 10, 15 (the third argument is the step) print(i) for i in range(10, 0, -1): # countdown: 10, 9, 8, ..., 1 print(i)
range(stop) starts at 0. range(start, stop) excludes stop. range(start, stop, step) lets you skip or count backwards.
3. The while Loop
Use for to go through a collection you already have. Use while to repeat until a condition is no longer true.
balance = 1000 days = 0 while balance < 2000: balance *= 1.05 # 5% growth per day days += 1 print(f"Doubled in {days} days.")
Use while when you don't know in advance how many times the loop must run: reading user input, retrying a failed request, running a simulation. Use for when you have a collection or a range.
4. break and continue
break exits the loop immediately. continue skips to the next iteration.
# Find the first negative number numbers = [4, 7, 12, -3, 5, -8] for n in numbers: if n < 0: print(f"First negative: {n}") break # done, so stop looking # Skip odd numbers for n in range(10): if n % 2 == 1: continue # skip the rest of this iteration print(n) # prints 0, 2, 4, 6, 8
continue is less common than break, and too many of them make a loop hard to read. If a loop has three continues, rewrite the conditions.
5. The Curious for...else
Python's loops can have an else clause. It runs only if the loop finished without reaching break. The usual use is a search:
numbers = [4, 7, 12, 8, 5] target = 9 for n in numbers: if n == target: print(f"Found {target}.") break else: print(f"{target} not found.") # runs because no break happened
Without for...else, you would need a found = False variable. With it, the "not found" code sits directly under the loop. while...else works the same way. Many Python programmers don't know this feature, so learn to recognise it, but use it only occasionally.
6. enumerate: Index and Value
If you need both the position and the item, don't use range(len(...)). Use enumerate:
fruits = ["apple", "banana", "cherry"] for i, fruit in enumerate(fruits): print(f"{i}: {fruit}") # 0: apple # 1: banana # 2: cherry # Start counting from a different number for i, fruit in enumerate(fruits, start=1): print(f"{i}. {fruit}") # 1. apple # 2. banana # 3. cherry
enumerate is the standard Python way to get the index as well as the item. Code that uses for i in range(len(fruits)): and then fruits[i] everywhere usually comes from someone who learned loops in another language first.
7. zip: Looping Over Two (or More) Iterables Together
names = ["Alice", "Bob", "Charlie"] ages = [30, 25, 35] for name, age in zip(names, ages): print(f"{name} is {age}") # Three or more works the same way cities = ["NYC", "LA", "SF"] for name, age, city in zip(names, ages, cities): print(f"{name} ({age}) lives in {city}")
If the iterables have different lengths, zip stops at the end of the shortest one. You get no error and no warning. The extra items are ignored:
short = [1, 2] long = [10, 20, 30, 40] print(list(zip(short, long))) # [(1, 10), (2, 20)]
If you'd rather get an error on mismatch, use zip(..., strict=True) (Python 3.10+).
8. reversed and sorted in Loops
# Iterate backwards without building a reversed list for n in reversed(range(1, 6)): print(n) # 5, 4, 3, 2, 1 # Iterate in sorted order without modifying the original scores = [42, 17, 89, 33] for s in sorted(scores): print(s) # 17, 33, 42, 89 for s in sorted(scores, reverse=True): print(s) # 89, 42, 33, 17
reversed() returns an iterator, so it doesn't build an extra list, but sorted() always builds a new list.
9. Nested Loops
A nested loop is a loop inside another loop. It is useful, but the number of steps grows quickly.
# Multiplication table for row in range(1, 4): for col in range(1, 4): print(f"{row * col:3d}", end=" ") print() # newline between rows
If the outer loop runs n times and the inner runs n times, that's n² total iterations, which is O(n²) time complexity. That is fine for small n. For n = 10_000, it is 100 million iterations, and your program becomes slow. Whenever you nest loops, ask whether there is a way without the inner loop. Often a dict lookup or a set membership test (both O(1) on average) can replace it.
10. List Comprehensions: A Preview
When the loop's only job is to build a new list, Python has a one-line form:
# The long way doubled = [] for x in range(5): doubled.append(x * 2) # The Pythonic way doubled = [x * 2 for x in range(5)] # [0, 2, 4, 6, 8] # With a filter evens = [x for x in range(20) if x % 2 == 0]
A full lesson covers this pattern: see comprehensions. For now, learn to recognise the shape: [expression for item in iterable if condition].
Common Mistakes
1. Modifying a list while iterating it
numbers = [1, 2, 3, 4, 5] # Bug: items get skipped because indices shift for n in numbers: if n % 2 == 0: numbers.remove(n) print(numbers) # [1, 3, 5] (right here only because no two even numbers sit next to each other) # Fix A: iterate over a copy for n in numbers[:]: # the [:] makes a shallow copy if n % 2 == 0: numbers.remove(n) # Fix B (better): build a new list numbers = [n for n in numbers if n % 2 != 0]
The second fix is faster and clearer, and it can't skip items, because it never changes the list it is looping over.
2. range(len(x)) when you wanted enumerate(x)
items = ["a", "b", "c"] # Bad style: experienced Python programmers avoid this for i in range(len(items)): print(i, items[i]) # Pythonic for i, item in enumerate(items): print(i, item)
This is the most common loop mistake that beginners make. Stop doing it early.
3. Forgetting to update the loop variable in while
i = 0 while i < 5: print(i) # forgot to add 1 to i, so the loop never ends!
If your program never stops, first look for a while loop whose condition never changes. Press Ctrl+C to stop the program, then add the missing update.
4. break only exits the innermost loop
# Searching a 2D grid for a value grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] target = 5 for row in grid: for cell in row: if cell == target: print("Found!") break # only breaks the inner loop # ...but the outer loop keeps going # Fix: move the loops into a function and use return def find(grid, target): for row in grid: for cell in row: if cell == target: return True # exits the whole function return False
Moving nested loops into a function with an early return is almost always cleaner than using a found variable.
5. Off-by-one in range
# "I want 1 through 10" for i in range(1, 10): print(i) # prints 1 to 9, and misses 10! # Correct for i in range(1, 11): print(i) # prints 1 to 10
range(start, stop) is half-open: stop is excluded. If you want the numbers 1 to N, including N, write range(1, N + 1). Or use range(N) and add 1 to each number.
🎯 Your Turn: FizzBuzz
This is a well-known interview question. Write fizzbuzz(n) that prints the numbers from 1 to n, with these rules:
- Multiples of 3 → print
"Fizz" - Multiples of 5 → print
"Buzz" - Multiples of both (i.e. 15) → print
"FizzBuzz" - Anything else → print the number itself
Skeleton:
def fizzbuzz(n): # TODO: loop from 1 to n inclusive # TODO: choose Fizz, Buzz, FizzBuzz or the number ... fizzbuzz(20) # 1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, # 11, Fizz, 13, 14, FizzBuzz, 16, 17, Fizz, 19, Buzz
Hint 1: The range
Rememberrange(1, n + 1) to get 1 through n inclusive. range(1, n) stops at n - 1.
Hint 2: Check the most specific case first
Order matters.15 is divisible by both 3 and 5. If you check i % 3 == 0 first, you'll print "Fizz" and miss "FizzBuzz". Put the combined check (divisible by 15, or divisible by 3 and 5) at the top of the chain.
Show full solution
def fizzbuzz(n): for i in range(1, n + 1): if i % 15 == 0: # most specific case first print("FizzBuzz") elif i % 3 == 0: print("Fizz") elif i % 5 == 0: print("Buzz") else: print(i) fizzbuzz(20)
A shorter version uses string concatenation. It needs no special check for "FizzBuzz": that case is produced by the two other checks together.
def fizzbuzz(n): for i in range(1, n + 1): output = "" if i % 3 == 0: output += "Fizz" if i % 5 == 0: output += "Buzz" print(output or i) # truthiness: "" is falsy, so print i
output or i uses the fact that an empty string is falsy. If output is "" (neither rule matched), the expression gives i instead. The function has two ifs, no elif chain and no special case for 15.
What You Learned
forfor iterables (lists, strings, dicts, ranges).whilefor "until a condition changes".range(start, stop, step): stop is excluded.range(N)starts at 0.breakexits,continueskips to the next iteration.for...else: theelseruns only if nobreakhappened. It is useful in search loops.enumerate(items): the right way to get the index and the value. Don't userange(len(items)).zip(a, b): loops over several iterables together and stops at the shortest. Usestrict=Trueto get an error if the lengths differ.reversedreturns an iterator, andsortedreturns a new list. Both work directly in afor.- Nested loops are O(n²). Where you can, replace the inner loop with a dict or set lookup.
- List comprehensions are the one-line
forfor building lists. - Don't mutate a list while iterating it. Build a new one with a comprehension instead.
Next: Tuples, the sequence that cannot be changed after you create it.
Practice this
on practicepython.inShort exercises that run in your browser and tell you what your code did, as well as whether a test passed.
Found this useful? Share it with a friend who’s learning Python.