PythonMastery
beginner 16 min read · lesson 11 of 19 in Python Fundamentals

Loops: for, while, and the Pythonic Patterns

1 · The lesson

read

Computers 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.

python
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.

python
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.

python
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.

python
# 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:

python
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:

python
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

python
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:

python
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

python
# 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.

python
# 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:

python
# 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

python
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)

python
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

python
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

python
# 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

python
# "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:

python
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 Remember range(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
python
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.

python
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

  • for for iterables (lists, strings, dicts, ranges). while for "until a condition changes".
  • range(start, stop, step): stop is excluded. range(N) starts at 0.
  • break exits, continue skips to the next iteration.
  • for...else: the else runs only if no break happened. It is useful in search loops.
  • enumerate(items): the right way to get the index and the value. Don't use range(len(items)).
  • zip(a, b): loops over several iterables together and stops at the shortest. Use strict=True to get an error if the lengths differ.
  • reversed returns an iterator, and sorted returns a new list. Both work directly in a for.
  • Nested loops are O(n²). Where you can, replace the inner loop with a dict or set lookup.
  • List comprehensions are the one-line for for 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.in

Short 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.