Python Lesson 7: while & for Loops Explained (range, break, continue)
Chapters: 0:00 Why Loops Matter 0:19 Key Terms, Plain English 0:38 The while Loop 0:56 The for Loop 1:13 range(): Counting Made Easy 1:34 break: Exit Early 1:53 continue: Skip This Round 2:09 The Sneaky loop-else 2:30 Mistake: Infinite while Loop 2:47 Mistake: Off-by-One with range 3:05 for vs while: Pick the Right Tool 3:27 Nested Loops: A Quick Look 3:46 Recap: What We Covered Master Python loops from scratch: while vs for, range() for counting, break to exit early, continue to skip iterations, and the sneaky loop-else clause. We'll also cover the two classic loop bugs (infinite while loops and off-by-one range errors) plus a quick look at nested loops so you know exactly which tool to reach for. #python #pythonloops #learnpython #coding #programmingtutorial
Introduction
Every program you'll ever write comes down to three things: sequence, selection, and repetition. Loops are that third piece, and they're the one that trips up beginners the most — not because the syntax is hard, but because knowing which loop to reach for, and when it will stop, takes a bit of practice.
If you've ever copy-pasted the same line of code five times because you needed to print five things, a loop is the fix. Instead of writing print(item) over and over, you write it once and let Python repeat it for you.
This matters far beyond toy examples. Processing a list of orders, validating every row in a spreadsheet, retrying a network call until it succeeds, searching a dataset for a match — all of this is loops. It's also one of the first things interviewers probe, because how you loop (and how you stop looping) reveals whether you actually understand control flow or you're just pattern-matching syntax you memorized.
In this lesson, we'll build a complete mental model: while loops, for loops, range(), break, continue, the often-misunderstood loop-else, the two classic bugs that catch almost every beginner, and when to reach for nested loops.
Problem Overview
Stated plainly, the problem loops solve is: how do I repeat a block of code multiple times without rewriting it?
Python gives you two main tools for this:
whileloop — repeats as long as a condition stays true. You don't necessarily know in advance how many times it'll run.forloop — walks through an iterable (a list, string, range, or anything you can step through item by item) and runs the block once per item. You know exactly what you're looping over.
Alongside these, you get control statements to steer the loop mid-flight:
break— stop the loop immediately, no matter what's left.continue— skip the rest of the current iteration and move to the next one.else(attached to a loop) — runs only if the loop finished naturally, without ever hitting abreak.
And you get range(), a way to generate a sequence of numbers on the fly so you can loop a specific number of times without needing a real list.
Example
# while loop
count = 0
while count < 3:
print(count)
count += 1
# Output: 0 1 2
# for loop over a real iterable
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# Output: apple banana cherry
# range()
for i in range(5):
print(i)
# Output: 0 1 2 3 4 (stops BEFORE 5)
# break
numbers = [4, 8, 15, 16, 23, 42]
for num in numbers:
if num == 15:
print("found it")
break
# Output: found it (16, 23, 42 are never checked)
# continue
for num in [1, 2, 3, 4]:
if num == 2:
continue
print(num)
# Output: 1 3 4 (2 is skipped, loop keeps going)
Each output follows directly from the rule the loop enforces: while checks a condition every pass, for walks a known sequence, range(5) produces 0 through 4 (five numbers, stopping before 5), break exits entirely the instant its condition is met, and continue skips only the current pass.
Intuition
Here's how an experienced engineer thinks about this, before writing a single line of code.
Ask yourself one question first: do I know exactly what I'm looping over, or am I waiting for something to become true?
- If you have a concrete collection — a list of users, a range of numbers, a string of characters — you already know the loop's boundaries. That's a
forloop. It's like handing out name tags from a list: you go until the list runs out, no risk of going forever. - If you're waiting on a condition — "keep asking the user for input until they type quit," "keep retrying until the server responds" — you don't know the number of iterations in advance. That's a
whileloop. It's like leaving a light on as long as someone's in the room.
Once you're inside a loop, the next question is: do I need to react to something mid-loop? If yes, that's where break and continue come in. Think of break as slamming the brakes the moment you find what you need — no reason to check the rest. Think of continue as skipping one track on a playlist without turning the music off.
The else clause is the part most engineers forget exists, and it solves a specific, recurring pattern: "did I finish searching without finding anything?" Instead of a separate found = False flag you set and check after the loop, for...else gives you that signal for free — else only fires if the loop completed without a break.
This intuition — "what am I looping over" then "do I need early exits" — is really the entire design space of loops. Everything else is syntax.
Brute Force Solution
There isn't a "brute force vs optimal" algorithm here in the traditional sense — loops are the primitive, not the problem. But it's worth calling out the naive way beginners often reach for a for loop when a while (or vice versa) would be more honest, and the naive way beginners simulate a "did I find it" flag manually.
Naive found-flag pattern (before knowing loop-else):
numbers = [4, 8, 15, 16, 23, 42]
found = False
for num in numbers:
if num == 4:
found = True
break
if not found:
print("not in the list")
- Idea: manually track whether the loop found something using an extra boolean variable.
- Advantages: works everywhere, easy to read for beginners, no special syntax knowledge required.
- Disadvantages: extra variable to manage, easy to forget to set it, adds a line of state that the loop itself already implicitly knows.
- Time complexity: O(n) — you may scan the whole list.
- Space complexity: O(1) — just one extra boolean.
Optimal Solution
The "optimal" version isn't faster in Big-O terms — it's cleaner, because it removes the manual flag entirely by using Python's built-in for...else:
numbers = [4, 8, 15, 16, 23, 42]
for num in numbers:
if num == 100:
print("found it")
break
else:
print("not in the list")
| Step | What happens |
|---|---|
| Loop starts | Python walks through numbers one at a time |
| Condition never true | 100 never matches any element |
| Loop exhausts the list | No break ever executes |
else block runs |
Because the loop completed without breaking |
The key mental shift: else on a loop doesn't mean "if the loop didn't run" — it means "if the loop ran to completion without a break." That's the whole trick, and once it clicks, you'll spot places to use it in search and validation logic constantly.
Python Code
def find_first_match(items, target):
"""Search items for target; return index if found, else -1."""
for index, value in enumerate(items):
if value == target:
return index
else:
# Only runs if the loop never hit 'return' inside the if
return -1
def countdown(start):
"""Print numbers from start down to 1 using a while loop."""
current = start
while current > 0:
print(current)
current -= 1 # must move toward the exit condition
print("Liftoff!")
def print_multiplication_grid(rows, cols):
"""Nested loop example: print a rows x cols multiplication table."""
for row in range(1, rows + 1):
for col in range(1, cols + 1):
print(row * col, end="\t")
print() # newline after each row
Code Walkthrough
enumerate(items)— gives you both the index and the value in one pass, avoiding a manual counter.if value == target: return index— areturninside a loop is effectively abreakthat also hands back data; the function exits immediately.- The
elseonfind_first_match'sforloop is technically redundant here sincereturn -1after the loop achieves the same thing — included to show the pattern, and worth removing in production code for clarity (see Common Mistakes below). countdown'scurrent -= 1is the single most important line in the function — remove it and thewhilecondition never turns false.print_multiplication_gridshows nested loops: the outer loop fixes a row, and the inner loop runs completely for every single row, which is why nested loops cost rows × cols operations, not rows + cols.
Dry Run
Trace find_first_match([4, 8, 15, 16, 23], 16):
| Iteration | index | value | value == 16? | Action |
|---|---|---|---|---|
| 1 | 0 | 4 | No | continue loop |
| 2 | 1 | 8 | No | continue loop |
| 3 | 2 | 15 | No | continue loop |
| 4 | 3 | 16 | Yes | return 3 |
Function returns 3 immediately — indices 4 (23) never get checked, because return exits the loop right away, just like break would.
Complexity Analysis
find_first_match: Time is O(n) in the worst case (target is last or absent — every element is checked once). Best case is O(1) if the first element matches. Space is O(1) — no extra data structures grow with input size.countdown: Time is O(n) where n isstart, since the loop runs exactlystarttimes. Space is O(1).print_multiplication_grid: Time is O(rows × cols) because the inner loop fully re-runs for every outer iteration. Space is O(1) beyond the printed output.
These are correct because a loop's cost is always the number of iterations times the cost of the loop body — with a single loop that's O(n), and with one loop nested inside another it multiplies to O(n × m).
Alternative Solutions
You could replace find_first_match with Python's built-in tools:
index = numbers.index(target) if target in numbers else -1
This is more concise but does two full passes in the worst case (in scans once, .index scans again), whereas the loop-based version scans once. For learning purposes and for interviews, writing the loop yourself is the expected answer — built-ins are for production code once you've proven you understand the underlying mechanics.
Edge Cases
- Empty input:
for num in []:simply never executes the body — no error, theelseclause (if present) still runs. - Duplicates: a search loop with
breakstops at the first match; if you need all matches, don'tbreak— collect results in a list instead. - Minimum values:
range(0)produces nothing at all;while False:never runs once. - Maximum values / large ranges:
range()doesn't build a list in memory — it's lazy — sorange(10_000_000)is cheap to create even though iterating fully still costs time. - Off-by-one boundaries: always double-check whether your stop value should be inclusive or exclusive;
range()is always exclusive of its stop.
Common Mistakes
- Forgetting to update the loop variable in a
whileloop — causes an infinite loop that freezes your program. Always confirm something inside the loop moves you toward the exit condition. - Assuming
range(stop)includesstop— it never does;range(1, 6)gives you 1–5, not 1–6. - Using
breakwhen you meantcontinue, or vice versa —breakexits the whole loop,continueonly skips the current pass. Mixing these up silently produces wrong output, not an error. - Misreading
loop-else— it does not mean "if the loop didn't run." It means "if the loop finished without abreak." An empty loop'selsestill runs, which surprises people. - Mutating a list while iterating over it — removing or adding items to a list inside a
for item in my_list:loop skips elements or raises unexpected behavior. Iterate over a copy (my_list[:]) if you need to modify the original.
Interview Questions
- What's the difference between
breakandcontinue, and can you give an example where using the wrong one produces incorrect output? - Explain when a
for...elseclause executes, using a concrete example. - Why does
range(5)stop at 4 instead of 5? Walk through the underlying reasoning. - How would you rewrite a
whileloop that has a known number of iterations as aforloop, and why might that be safer? - What's the time complexity of two nested loops, each running n times, and why?
Similar Problems
- LeetCode 27 (Remove Element) — tests in-place iteration and index management, a common
while/index pitfall. - LeetCode 209 (Minimum Size Subarray Sum) — sliding window pattern, built entirely on nested/two-pointer loop control.
- LeetCode 704 (Binary Search) — a
whileloop with a shrinking search space, directly builds on the "when does the loop stop" intuition covered here. - LeetCode 202 (Happy Number) — relies on loop termination reasoning similar to avoiding infinite
whileloops.
Key Takeaways
- Use
forwhen you know exactly what you're looping over — it's safer and less likely to run forever. - Use
whilewhen you're waiting for a condition to become true, and always make sure something inside the loop changes that condition. range()generates numbers lazily; its stop value is always excluded.breakexits the entire loop immediately;continueskips only the current iteration.loop-elseruns only if the loop finished without abreak— a clean way to express "searched and found nothing."- Nested loops multiply cost — only reach for them when you genuinely have two dimensions to walk through.
Watch the Video
For the full walkthrough with live coding and visual explanations of every concept above, watch the video here: {LINK}
About the Series
This lesson is part of the Daily Python LeetCode series, a day-by-day walkthrough of Python fundamentals and interview-style problem solving — built for developers who want to go from "I know the syntax" to "I understand why it works." Each lesson builds on the last, so if loops felt shaky before, they shouldn't anymore.
Call To Action
If this helped things click, subscribe on YouTube so you don't miss the next lesson, and subscribe to the Substack newsletter for the written breakdown delivered straight to your inbox. Drop a comment with what tripped you up (loop-else gets almost everyone), and share this with a fellow learner who's still fighting with infinite loops.
The solution
for num in [1, 3, 5]:
if num == 4:
break
else:
print("no even number found")Ready to try it yourself? Solve General problems with instant feedback in the practice sandbox.
Practice now →


