The Story That Explains Loops
Every program you'll ever write is packed with repetition: send an email to each customer, add up every number in a list, keep asking for a password until the user gets it right, play frame after frame until the game ends. Without loops, you'd have to write each step out by hand — an impossible way to build software. With loops, you write the task once, and tell Python to run it as many times as needed.
Python has two main loops — for (walk through a collection) and while (repeat while a condition holds) — plus three control keywords: break (jump out early), continue (skip to the next round), and pass (do literally nothing). Master these five and you've unlocked one of the most powerful tools in programming.
The for Loop — Walking Through a Collection
A for loop takes an iterable (something you can walk through, item by item) and executes its body once per item. The name of your loop variable — for x in ... — is bound to each element in turn.
The Flow of a for Loop
Basic Syntax
# ─── LOOP OVER A LIST ───────────────────────────────────
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(f"I like {fruit}")
# OUTPUT:
# I like apple
# I like banana
# I like cherry
# ─── LOOP OVER A STRING (characters) ────────────────────
for letter in "hello":
print(letter)
# OUTPUT: h, e, l, l, o (each on its own line)
# ─── LOOP OVER A TUPLE ──────────────────────────────────
for item in ("red", "green", "blue"):
print(item)
# ─── LOOP OVER A DICTIONARY (keys by default) ──────────
scores = {"Alice": 92, "Bob": 85}
for name in scores:
print(f"{name}: {scores[name]}")
# ─── LOOP OVER A SET (order not guaranteed) ────────────
for n in {3, 1, 4, 1, 5}:
print(n)
Unlike C or Java, Python's for doesn't use a counter and condition. It's a for-each loop — it walks through the elements of an iterable directly. If you need an index, use enumerate(). If you need a counter, use range(). Never write for i in range(len(lst)): just to get at lst[i] — use for item in lst: instead.
The range() Function — Your Numeric Companion
When you need to loop a specific number of times, or over a sequence of numbers, reach for range(). It generates numbers on demand — cheap, fast, and memory-efficient (even range(10_000_000) uses almost no memory).
# ─── COUNT FROM 0 TO N-1 ────────────────────────────────
for i in range(5):
print(i) # 0, 1, 2, 3, 4
# ─── COUNT FROM A TO B-1 ────────────────────────────────
for i in range(1, 6):
print(i) # 1, 2, 3, 4, 5
# ─── COUNT WITH A STEP ──────────────────────────────────
for i in range(0, 20, 3):
print(i) # 0, 3, 6, 9, 12, 15, 18
# ─── COUNT DOWN (negative step) ─────────────────────────
for i in range(10, 0, -1):
print(i) # 10, 9, 8, ..., 1
# ─── REPEAT SOMETHING N TIMES (don't need i) ───────────
for _ in range(3):
print("Hello!") # prints Hello! three times
When you loop and don't need the loop variable, use _ (underscore). It's a Python convention meaning "I know I'm getting a value, but I'm ignoring it." Readers instantly understand your intent, and linters won't warn about unused variables.
The while Loop — Repeat Until Condition Ends
A while loop repeats as long as a condition stays True. Before each iteration, Python checks the condition. If it's True, run the body; if it's False, exit the loop. Unlike for, you don't specify how many times — you specify when to stop.
The Flow of a while Loop
# ─── COUNTDOWN ──────────────────────────────────────────
count = 5
while count > 0:
print(f"T-minus {count}")
count -= 1 # MUST modify — else infinite loop!
print("Liftoff!")
# OUTPUT:
# T-minus 5
# T-minus 4
# T-minus 3
# T-minus 2
# T-minus 1
# Liftoff!
# ─── INPUT VALIDATION (classic while use case) ──────────
password = ""
while len(password) < 8:
password = input("Enter a password (8+ chars): ")
print("Password accepted")
# ─── SIMULATE UNTIL STABLE ──────────────────────────────
temperature = 100
while temperature > 25:
temperature -= 5 # cool by 5 each step
print(f"Now {temperature}°")
print("Cool enough.")
If nothing in the body ever makes the condition False, the loop runs forever. Your program hangs; you have to force-kill it. Every while body MUST do something that eventually changes the condition — decrement a counter, update a state, break out on a signal. If you catch yourself with while True:, make sure there's a break inside.
for vs while — When to Use Which
| Situation | Example |
|---|---|
| You have a collection to walk through | for item in list |
| You know the exact count | for i in range(10) |
| Processing each character of a string | for c in text |
| Iterating dict keys/values/items | for k, v in d.items() |
| Fixed number of retries | for _ in range(3) |
| Situation | Example |
|---|---|
| Number of iterations is unknown up front | Wait for user input |
| Loop depends on external state | Retry until server responds |
| Simulation until stable | Physics steps until at rest |
| Event loop / game loop | while running: |
| Reading from a stream | Until EOF or sentinel value |
If you can express your loop as "for each X in Y", use for. It's the safer choice — Python guarantees you'll finish because the iterable is finite. Reach for while only when the stopping condition truly depends on something you're computing or observing during the loop.
break — Exit the Loop Immediately
break is the emergency exit. When Python hits a break, it jumps out of the enclosing loop right away — no more iterations, no more condition checks. Use it when you've found what you were looking for, or when something's gone wrong and there's no point continuing.
# ─── SEARCH AND STOP ────────────────────────────────────
numbers = [4, 2, 7, 1, 9, 3, 6]
for n in numbers:
if n == 9:
print("Found 9!")
break # stop looking — we're done
print(f"Checking {n}...")
# OUTPUT:
# Checking 4...
# Checking 2...
# Checking 7...
# Checking 1...
# Found 9!
# ─── COMMON while True + break PATTERN ─────────────────
while True:
answer = input("Continue? (yes/no): ")
if answer.lower() == "no":
break # exit the infinite loop
print("Ok, once more...")
print("Goodbye!")
# ─── BREAK ONLY EXITS THE INNERMOST LOOP ────────────────
for i in range(3):
for j in range(3):
if j == 2:
break # only breaks inner loop!
print(i, j)
# OUTPUT:
# 0 0 0 1 1 0 1 1 2 0 2 1
# Outer loop still ran fully — break didn't escape it.
continue — Skip to the Next Iteration
continue is the "skip this one" button. When Python hits it, the rest of the current iteration is skipped, and the loop moves on to the next item (or re-checks the condition, for a while loop).
# ─── SKIP NEGATIVE NUMBERS ──────────────────────────────
numbers = [5, -3, 8, -1, 4, -7, 2]
for n in numbers:
if n < 0:
continue # skip negatives entirely
print(f"Processing {n}")
# OUTPUT:
# Processing 5
# Processing 8
# Processing 4
# Processing 2
# ─── FILTER + PROCESS PATTERN ──────────────────────────
words = ["apple", "", " ", "banana", "cherry", ""]
for word in words:
if not word.strip(): # empty or whitespace
continue # skip garbage
print(word.upper())
# OUTPUT: APPLE, BANANA, CHERRY
# ─── ONLY EVEN NUMBERS ─────────────────────────────────
total = 0
for n in range(1, 11):
if n % 2 != 0:
continue # skip odd numbers
total += n
print(total) # 30 (2+4+6+8+10)
break = "I'm done with the whole loop —
get me out."
continue = "I'm done with THIS iteration —
give me the next one."
Both are useful; both are readable when used sparingly. Overusing them turns a loop into
a maze — reach for filter-first patterns (list comprehensions, guard clauses) when the
logic gets tangled.
pass — The "Do Nothing" Placeholder
pass is Python's no-op. It literally does nothing. Its whole reason to exist is that Python requires every code block to have at least one statement — you can't have an empty if, function, or class. pass is a legal placeholder that says "yes, there's supposed to be code here, but not yet."
# ─── PLACEHOLDER IN A FUNCTION YOU'LL WRITE LATER ───────
def send_email(recipient, message):
pass # TODO: implement
# ─── PLACEHOLDER IN AN if BLOCK ─────────────────────────
if user.is_admin:
pass # admins are allowed — do nothing special
else:
log("non-admin access denied")
raise PermissionError()
# ─── PLACEHOLDER IN A LOOP (skip processing) ────────────
for n in range(10):
if n % 2 == 0:
pass # explicitly «do nothing» for evens
else:
print(f"odd: {n}")
# ─── EMPTY CLASS DECLARATION ────────────────────────────
class Placeholder:
pass # works — legal empty class
pass vs continue vs break — Not the Same Thing!
| Keyword | What It Does | Effect on the Loop |
|---|---|---|
| pass | Nothing at all | Loop continues normally — next statement runs |
| continue | Skips rest of current iteration | Jumps back to check condition for next iteration |
| break | Exits the loop entirely | Loop ends; execution moves past the loop |
# ─── SEE THE DIFFERENCE IN ONE PROGRAM ──────────────────
for n in range(5):
if n == 2:
pass # no effect — just documentation
print(n, end=" ")
# OUTPUT: 0 1 2 3 4 ← everything printed
print()
for n in range(5):
if n == 2:
continue # skip printing 2
print(n, end=" ")
# OUTPUT: 0 1 3 4 ← 2 skipped
print()
for n in range(5):
if n == 2:
break # stop the loop
print(n, end=" ")
# OUTPUT: 0 1 ← stopped at 2
The else Clause — Python's Loop Superpower
Python loops have a feature almost no other language has: an else block that runs only if the loop finished normally (without hitting a break). It's perfect for the "search and confirm not found" pattern.
# ─── FIND AND REPORT MISSING ────────────────────────────
numbers = [1, 3, 5, 7, 9]
target = 4
for n in numbers:
if n == target:
print(f"Found {target}!")
break
else:
print(f"{target} not in the list") # runs — no break happened
# OUTPUT: 4 not in the list
# ─── WITH break — else IS SKIPPED ──────────────────────
for n in numbers:
if n == 5:
print("Found 5!")
break
else:
print("Not found") # does NOT run
# OUTPUT: Found 5!
# ─── while ALSO SUPPORTS else ──────────────────────────
i = 0
while i < 5:
print(i)
i += 1
else:
print("Loop finished cleanly") # runs — no break used
The else clause on a loop confuses many people because of its name. A better mental model: read it as "loop-else = no break happened". The block runs only when the loop completes its full iteration without an early exit. This makes it perfect for "we searched and didn't find it" messages.
Nested Loops — Loops Inside Loops
You can put one loop inside another. The inner loop runs completely for each iteration of the outer loop. This is how you handle 2D grids, generate combinations, or process nested data.
# ─── MULTIPLICATION TABLE (3x3) ─────────────────────────
for i in range(1, 4):
for j in range(1, 4):
print(f"{i}x{j}={i*j:2d}", end=" ")
print() # newline after each row
# OUTPUT:
# 1x1= 1 1x2= 2 1x3= 3
# 2x1= 2 2x2= 4 2x3= 6
# 3x1= 3 3x2= 6 3x3= 9
# ─── ITERATE OVER A 2D GRID ─────────────────────────────
grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
for row in grid:
for cell in row:
print(cell, end=" ")
print()
# ─── BREAKING OUT OF NESTED LOOPS ──────────────────────
# break exits ONLY the innermost loop
found = False
for i in range(3):
for j in range(3):
if i * j >= 4:
found = True
break # breaks inner only
if found:
break # check & break outer manually
print(f"Stopped at ({i}, {j})")
Two nested loops over 1000 items each is 1,000,000 operations. Three loops is a billion. Before nesting, ask if there's a smarter way — a set for membership tests, a dict lookup, or a comprehension. If you truly need nesting, keep the inner range small and the operations cheap.
Common Loop Patterns You'll Use Forever
# ─── ACCUMULATOR: sum a list ───────────────────────────
numbers = [10, 20, 30, 40]
total = 0 # start empty
for n in numbers:
total += n # accumulate
print(total) # 100
# ─── ACCUMULATOR: build a string ───────────────────────
words = ["Python", "is", "fun"]
sentence = ""
for w in words:
sentence += w + " "
print(sentence.strip()) # Python is fun
# ─── SEARCH: find first even number ────────────────────
nums = [7, 3, 9, 12, 15]
for n in nums:
if n % 2 == 0:
print(f"First even: {n}")
break
else:
print("No even numbers")
# ─── FILTER: keep only positives ────────────────────────
data = [3, -1, 7, -4, 2, 0]
positives = []
for n in data:
if n > 0:
positives.append(n)
print(positives) # [3, 7, 2]
# Same thing as a comprehension (Pythonic!)
positives = [n for n in data if n > 0]
# ─── MAP: transform each value ─────────────────────────
prices = [10.0, 20.0, 30.0]
with_tax = []
for p in prices:
with_tax.append(p * 1.15)
print(with_tax) # [11.5, 23.0, 34.5]
# Comprehension version
with_tax = [p * 1.15 for p in prices]
Common Traps and How to Avoid Them
Complete Practice Program — Number Guessing Game
# guessing_game.py — every loop concept in one program
import random
def get_valid_guess(low, high):
"""Loop until user enters a valid number (uses while + break)."""
while True: # infinite loop with break exit
raw = input(f"Guess ({low}-{high}): ")
if not raw.strip():
print(" Empty input — try again.")
continue # skip rest, ask again
try:
n = int(raw)
except ValueError:
print(" Not a number — try again.")
continue
if not low <= n <= high:
print(f" Out of range — must be {low}-{high}.")
continue
return n # valid — break out via return
def play_round(low, high, max_tries):
"""One round using a for loop with break and for-else."""
secret = random.randint(low, high)
print(f"\nI'm thinking of a number between {low} and {high}.")
print(f"You have {max_tries} guesses.\n")
for attempt in range(1, max_tries + 1):
guess = get_valid_guess(low, high)
if guess == secret:
print(f"🎉 Correct! You got it in {attempt} tries.")
return True
elif guess < secret:
print(f" Too low. ({max_tries - attempt} guesses left)\n")
else:
print(f" Too high. ({max_tries - attempt} guesses left)\n")
else: # for-else: only runs if no break/return
print(f"😕 Out of tries. The number was {secret}.")
return False
def main():
# Accumulator: track wins and losses
wins, losses = 0, 0
while True:
won = play_round(low=1, high=20, max_tries=5)
if won:
wins += 1
else:
losses += 1
again = input("\nPlay again? (y/n): ").strip().lower()
if again != "y":
break # exit the game loop
print(f"\nFinal score: {wins} wins, {losses} losses.")
if __name__ == "__main__":
main()
This one program uses: while True with break (input validation), continue (skip invalid input), for with range (guess attempts), for-else (out-of-tries message), an accumulator (score tracking), and a return to exit both a function and its enclosing loop. Read it a few times — every pattern in this tutorial is in there.