Lesson 3

Loops

Home › Courses › Python › Lesson 3
🐍 Lesson 3 · 15 minutes

Loops.
Do it again. Smarter.

A loop runs the same code multiple times — once per item in a list, once per number in a range, or over and over until a condition becomes false. Without loops, every program would be a hundred copies of the same line. With loops, five lines handle a million items.

~15 minutes Beginner Python 3.x
Python path Lesson 2 for loops range() while loops break / continue enumerate() Nested loops Challenge

Python has two loops: for and while. A for loop iterates over a sequence (list, tuple, string, or range()) — once per item. A while loop runs as long as a condition is true — you control when it stops. Use for when you know what to iterate. Use while when you're waiting for something to change. Both support break (exit early) and continue (skip this iteration).

Before this lesson: you should be comfortable with lists and tuples. If not, read Lesson 2 first.

🔁 for loops — iterate over anything

The workhorse of Python. If you learn one loop, learn this one.

A for loop takes each item from a sequence and runs the loop body with that item in a variable. The loop ends automatically when it runs out of items. No counter to manage, no condition to check — Python handles it.

Python
# Loop over a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# apple
# banana
# cherry

# Loop over a string — each character
for char in "hello":
print(char)
# h e l l o (one per line)

# Loop over a tuple
for coord in (1, 2, 3):
print(coord)

# Accumulate inside a loop
nums = [1, 2, 3, 4, 5]
total = 0
for n in nums:
total += n
print(total) # 15

Any iterable works

Lists, tuples, strings, dicts, sets, files, range() — if you can loop over it, for handles it. Same syntax, different data.

No manual index

You don't need i = 0; while i < len(x); i += 1. The loop gives you each item directly. Python handles the counter internally.

Variable scope

The loop variable (fruit above) keeps its last value after the loop ends. It's not scoped to the loop body — a common source of bugs.

Empty iterable = no iterations

Looping over [] runs the body zero times. No error, just silence. This is why checking "did the loop run" needs a flag or a return value.

#️⃣ range() — loop N times

When you don't have a list but you know how many times to run.

When you need to run a loop a specific number of times — or you want numbers from a sequence — use range(). It generates numbers on the fly, not all at once, so it works for a million iterations without using any memory.

Python
# range(stop) — 0 to stop-1
for i in range(5):
print(i)
# 0, 1, 2, 3, 4

# range(start, stop) — start to stop-1
for i in range(1, 6):
print(i)
# 1, 2, 3, 4, 5

# range(start, stop, step)
for i in range(0, 10, 2):
print(i)
# 0, 2, 4, 6, 8

# Countdown
for i in range(5, 0, -1):
print(i)
# 5, 4, 3, 2, 1

# Sum of 1 to 100
total = sum(range(1, 101))
print(total) # 5050

Stop is always exclusive. range(5) gives 5 numbers (0–4), not 6. range(1, 5) gives 4 numbers (1–4), not 5. If you want 1 through 5 inclusive, write range(1, 6). This off-by-one is the single most common loop bug.

♾️ while loops — run until a condition changes

For when you don't know how many iterations you need.

A while loop runs as long as its condition is True. Each iteration checks the condition first, then runs the body. If the condition is False at the start, the loop body never runs. If it never becomes False, the loop runs forever.

Python
# Countdown with while
count = 5
while count > 0:
print(count)
count -= 1 # CRITICAL — update the variable
print("Liftoff!")
# 5 4 3 2 1 Liftoff!

# Wait for valid input
while True:
answer = input("Continue? (y/n): ")
if answer.lower() == "y":
break
elif answer.lower() == "n":
print("Goodbye")
break
else:
print("Please enter y or n")

# Collatz sequence (the "3n + 1" problem)
n = 27
steps = 0
while n != 1:
n = n // 2 if n % 2 == 0 else 3 * n + 1
steps += 1
print(f"Reached 1 in {steps} steps")

The infinite loop danger. A while loop only stops when its condition becomes False. If you forget to update the variable the condition checks — or update a different one by mistake — the loop runs forever. Press Ctrl+C in the terminal to kill it. Always check: what changes inside this loop to eventually make the condition false?

Use for when you can

If you know what to iterate (a list, a range), use for. It can't accidentally be infinite — it ends when the sequence is exhausted.

Use while when you must

Retry loops, waiting for input, reading until EOF, game loops. Situations where "how many times" isn't known in advance.

Use while True + break for validation

The while True pattern with an explicit break is idiomatic for "keep asking until they give valid input." Clearer than a flag variable.

Check your exit condition

Before writing a while loop, ask: what makes this false? If you can't point to a line inside the loop that changes it, you have a bug.

✋ break & continue — control the flow

Two keywords that change what the loop does next.

break exits the loop immediately. continue skips the rest of this iteration and jumps to the next one. Both work in for and while loops. Neither is required — you can write every loop without them — but used well, they eliminate deeply nested if blocks.

Python
# break: exit the loop early
nums = [3, 7, 2, 8, 5]
for n in nums:
if n == 8:
print("Found 8, stopping")
break
print(n)
# 3 7 2 Found 8, stopping

# continue: skip this iteration
for n in nums:
if n % 2 != 0:
continue # skip odd numbers
print(n)
# 2 8

# break in while: user quits
while True:
cmd = input("Enter command (or 'quit'): ")
if cmd == "quit":
break
print(f"You typed: {cmd}")

Use break to replace deep nesting. If you find yourself writing an if inside an if inside an if, consider flipping the condition and using continue. Same logic, one indentation level. Example: if not valid: continue at the top of the loop avoids nesting the whole body inside if valid:.

🔢 enumerate() — index and value together

The Pythonic way to loop with a counter.

Often you need both the item and its position. In other languages you'd write a counter and increment it. In Python, enumerate() gives you both at once. It's cleaner, less error-prone, and the idiomatic way to do this.

Python
fruits = ["apple", "banana", "cherry"]

# Get (index, item) pairs
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# 0: apple
# 1: banana
# 2: cherry

# Start counting from 1 instead of 0
for rank, fruit in enumerate(fruits, start=1):
print(f"{rank}. {fruit}")
# 1. apple
# 2. banana
# 3. cherry

# Loop over a dictionary with index
scores = {"Alice": 92, "Bob": 85, "Charlie": 98}
for i, (name, score) in enumerate(scores.items()):
print(f"{i + 1}. {name}: {score}")

Don't do range(len(list)). If you're writing for i in range(len(items)): print(items[i]), stop. Use for i, item in enumerate(items): print(item). It's the same result but readable, and you don't have to look up the item inside the loop.

📚 Nested loops

A loop inside a loop. Powerful, but use sparingly.

Python
# Multiplication table (5x5)
for i in range(1, 6):
for j in range(1, 6):
print(f"{i*j:3}", end=" ")
print() # newline after each row

# Loop over nested data (list of lists)
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]

for row in matrix:
for value in row:
print(value, end=" ")
print()
# 1 2 3
# 4 5 6
# 7 8 9

# Sum everything in the matrix
total = 0
for row in matrix:
for value in row:
total += value
print(total) # 45

# Same thing — one line with sum()
print(sum(sum(row) for row in matrix)) # 45

break exits only the innermost loop. If you have three nested loops and call break in the innermost one, only that loop exits — the two outer loops keep running. If you need to break out of everything at once, either return from a function or use a flag variable checked in each loop.

🔍 What's actually happening

The mental model that makes loops click.

1

for calls iter() then repeatedly calls next()

When Python sees for x in items:, it calls iter(items) to get an iterator, then calls next() on it repeatedly. Each next() returns the next item. When next() raises StopIteration, the loop ends. This is why you can loop over anything — as long as it has an iter method, it works.

2

range() doesn't build a list

In Python 3, range(1000000) doesn't allocate a million numbers in memory. It creates a range object that generates each number on demand. Same memory as range(10) — a constant few bytes. If you need the actual list, wrap it: list(range(10)).

3

while only checks the condition at the top

Each iteration of a while loop starts by checking the condition. If it's True, the body runs. If False, the loop ends and the program continues below. The condition is not checked mid-body — so if something inside the body makes it false, the rest of that iteration still runs before the loop exits.

4

break and continue are jumps

break jumps to the line after the loop. continue jumps to the next iteration (back to the for or while at the top). Neither one runs any else clause on the loop (Python has an else on loops — rarely used, but it exists). Understanding this makes debugging loops much faster.

🏆 Challenge

Three tasks. Do all three — they build on each other.

Task 1 — FizzBuzz

Print numbers 1 to 20. But for multiples of 3, print "Fizz" instead of the number. For multiples of 5, print "Buzz". For multiples of both 3 and 5, print "FizzBuzz".

This is the most famous interview question in programming. It's simple, but it tests whether you understand loops and conditionals together.

Task 2 — Find the maximum

Given nums = [3, 7, 2, 9, 4, 1, 8], find the largest number without using the built-in max(). Start with largest = nums[0] and loop through the list. If you find a number bigger, update largest. Print the result.

Then rewrite it using max() — notice how much shorter it is. This shows you what a loop actually does.

Task 3 — Times table

Print a 5×5 multiplication table using nested loops. Format each number to width 3 so columns align.

Expected output:

1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25

Hint: use print(f"{i*j:3}", end=" ") inside the inner loop, then print() after the inner loop to start a new row.

⚠️ Common mistakes

The eight loop bugs that catch every beginner.

1. Infinite while loop — forgot to update the variable

while count > 0: print(count) runs forever. You forgot count -= 1. Fix: make sure something inside the loop changes the variable the condition checks. Press Ctrl+C to kill the runaway loop.

2. range() stop is exclusive

range(5) gives 0–4, not 0–5. range(1, 10) gives 1–9, not 1–10. If you want 1 through 10, write range(1, 11). Off-by-one bugs in loops almost always come from this.

3. Modifying a list while iterating it

for item in mylist: mylist.remove(item) skips items and can raise IndexError. The list shrinks while the loop is walking through it. Fix: iterate over a copy (for item in mylist[:]) or build a new list with a comprehension.

4. break inside nested loops only exits one loop

Three nested loops, break in the innermost — only that loop exits. The outer two keep running. Fix: return from a function, or use a flag variable checked in each loop.

5. for i in range(len(x)) instead of enumerate

for i in range(len(items)): print(items[i]) works but is un-Pythonic. Fix: for i, item in enumerate(items): print(item). Readable, no indexing bugs, faster.

6. Updating the wrong variable in a while loop

while x < 10: y += 1 — you're updating y but the condition checks x. Loop runs forever. Fix: check that the variable you're updating is the same one in the condition.

7. The loop variable keeps its last value

After for x in [1, 2, 3]: pass, x is 3 — the loop variable persists after the loop ends. If the loop never ran (empty iterable), x is undefined. Don't rely on the loop variable after the loop finishes.

8. Using = instead of == in the condition

while x = 5: is a syntax error. while x == 5: is what you meant. Python catches this — but only because assignment returns a value, so the parser rejects it. In other contexts the confusion is silent.

➡️ What's next

Loops open up the rest of the beginner track.

❓ Frequently asked questions

What is the difference between a for loop and a while loop in Python?

A for loop runs a fixed number of times — once for each item in an iterable (list, tuple, string, range). A while loop runs as long as a condition is true — potentially zero times or infinitely. Use for when you know what to iterate over. Use while when you're waiting for a condition to become false (reading input until valid, retrying until success).

How does Python's range() function work?

range(stop) generates numbers 0 to stop-1. range(start, stop) generates start to stop-1. range(start, stop, step) adds a step. The stop value is always exclusive — range(5) gives 0, 1, 2, 3, 4, not 5. range() returns a lazy iterator in Python 3, not a list, so you can wrap it in list() if you need actual numbers in memory.

What does break do in a Python loop?

break exits the loop immediately, skipping any remaining iterations. The program continues with the first line after the loop. Use it when you've found what you're looking for and don't need to keep iterating — searching a list, waiting for a valid input, or breaking out of a runaway loop. break only exits the innermost loop, not all enclosing loops.

What does continue do in a Python loop?

continue skips the rest of the current iteration and jumps to the next one. The loop doesn't exit — it just skips this one item. Use it to filter inside a loop when you'd otherwise need a big if block. For example: for x in nums: if x < 0: continue; print(x) — this skips negative numbers and prints the rest.

How do I loop with both index and value in Python?

Use enumerate(): for i, value in enumerate(my_list): print(i, value). enumerate() yields (index, item) tuples starting at 0. Add a start argument to offset the index: enumerate(my_list, start=1) begins at 1. This is more Pythonic than range(len(my_list)) and reading my_list[i] separately.

How do I loop over a dictionary in Python?

Loop over a dict directly to get keys: for key in my_dict. Use .values() to get values: for value in my_dict.values(). Use .items() to get both: for key, value in my_dict.items(). Use .items() 90% of the time — it's the most common pattern and avoids looking up the value again inside the loop.

Why is my while loop infinite?

Because the condition never becomes False. The most common causes: (1) you forgot to update the loop variable inside the loop, (2) you're updating a different variable than the one in the condition, (3) you're comparing with = instead of ==, or (4) the condition is always True by construction. Add a print or counter inside the loop to see what's happening. Press Ctrl+C to break out of an infinite loop.

How do I write a loop in one line in Python?

Use a list comprehension: [x*2 for x in range(5)]. This produces [0, 2, 4, 6, 8] in one line. For side effects, you can write a loop on one line with a semicolon (for x in nums: print(x)) but it's considered un-Pythonic — use a normal multi-line loop. List comprehensions are the one-line loop pattern that's actually idiomatic.

🌐 External resources

← Back to Python track
You're in the Python track · Lesson 3