Control Flow
Comprehensive guide on Control Flow.
Control Flow
Overview#
Control flow statements determine the order in which a program's
instructions execute. Python provides conditional statements (if,
elif, else), loops (for, while), loop control statements
(break, continue, else on loops), and, since Python 3.10, structural
pattern matching with match. This notebook covers all of these in depth,
including truthiness rules, common iteration helpers, nested loop control,
and pitfalls that trip up beginners.
1. Conditional Statements#
Python 3.12 (WASM)age = 20
if age < 13:
category = "child"
elif age < 20:
category = "teenager"
else:
category = "adult"
print(category) # adult
Comparison and logical operators#
| Operator | Meaning |
|---|---|
==, != | Equal to, not equal to |
<, >, <=, >= | Less/greater than (or equal) |
and, or, not | Logical combination |
in, not in | Membership test |
is, is not | Identity comparison |
Python 3.12 (WASM)name = "Alice"
if name in ("Alice", "Bob") and age >= 18:
print("Access granted")
Chained comparisons#
Python allows chaining comparisons in a way that reads naturally and evaluates each part only once.
Python 3.12 (WASM)x = 5
print(0 < x < 10) # True, equivalent to (0 < x) and (x < 10)
print(10 < x < 20) # False
The conditional (ternary) expression#
Python 3.12 (WASM)status = "adult" if age >= 18 else "minor"
# Can be nested, but readability suffers quickly — use sparingly
grade = "A" if score >= 90 else "B" if score >= 80 else "C"
Truthiness: what counts as False#
Every object in Python has an implicit boolean value used in if
statements, even if it isn't a literal True/False.
| Falsy values | Truthy values |
|---|---|
False, None, 0, 0.0 | Any nonzero number |
"" (empty string) | Any non-empty string |
[], (), {}, set() | Any non-empty collection |
Python 3.12 (WASM)items = []
if items:
print("has items")
else:
print("empty") # this runs — empty list is falsy
# Idiomatic emptiness check
if not items:
print("nothing to process")
2. for Loops#
A for loop iterates over any iterable — lists, tuples, strings,
dictionaries, ranges, files, and custom objects that implement iteration.
Python 3.12 (WASM)fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 10, 2): # start, stop, step -> 2, 4, 6, 8
print(i)
Useful iteration helpers#
Python 3.12 (WASM)fruits = ["apple", "banana", "cherry"]
# enumerate: get index + value together
for i, fruit in enumerate(fruits):
print(i, fruit) # 0 apple, 1 banana, 2 cherry
for i, fruit in enumerate(fruits, start=1): # start counting from 1
print(i, fruit)
# zip: iterate multiple sequences in parallel
prices = [1.5, 0.8, 2.0]
for fruit, price in zip(fruits, prices):
print(f"{fruit}: ${price}")
# reversed: iterate backwards
for fruit in reversed(fruits):
print(fruit)
# sorted with iteration
for fruit in sorted(fruits, reverse=True):
print(fruit)
Looping over a dictionary#
Python 3.12 (WASM)scores = {"Alice": 90, "Bob": 85}
for name in scores: # iterates keys by default
print(name)
for name, score in scores.items(): # keys and values together
print(f"{name}: {score}")
3. while Loops#
A while loop repeats as long as a condition remains true. Useful when
the number of iterations isn't known ahead of time — waiting for user
input, polling until a condition is met, and so on.
Python 3.12 (WASM)count = 0
while count < 5:
print(count)
count += 1
while with a sentinel value#
Python 3.12 (WASM)responses = []
while True:
answer = input("Enter a value (or 'done'): ")
if answer == "done":
break
responses.append(answer)
Avoiding infinite loops#
Always ensure the loop's condition will eventually become false — update the relevant variable inside the loop body.
Python 3.12 (WASM)# Dangerous: condition never changes
# while True:
# print("forever")
# Safe pattern with a clear exit condition
attempts = 0
max_attempts = 3
while attempts < max_attempts:
success = try_connection()
if success:
break
attempts += 1
else:
print("All attempts failed")
4. break, continue, and Loop else#
Python 3.12 (WASM)# break: exit the loop early, entirely
for n in range(10):
if n == 5:
break
print(n) # prints 0-4
# continue: skip the rest of this iteration, move to the next
for n in range(5):
if n % 2 == 0:
continue
print(n) # prints 1, 3
# else on a loop: runs only if the loop completes WITHOUT break
for n in range(5):
if n == 10:
break
else:
print("Loop completed without break") # this runs
The else clause on loops is a Python-specific feature that often
surprises newcomers coming from other languages — it does not mean "if the
loop didn't run," but "if the loop finished normally, without a break."
A common real use case is searching for something and reporting failure
only if nothing was found:
Python 3.12 (WASM)target = 7
numbers = [1, 3, 5, 9]
for n in numbers:
if n == target:
print("Found it")
break
else:
print("Not found") # runs because the loop never broke
5. Nested Loops and Loop Control#
Python 3.12 (WASM)for i in range(3):
for j in range(3):
print(f"({i}, {j})")
break and continue only affect the innermost loop they're written
in — they cannot directly exit an outer loop.
Breaking out of nested loops#
Python has no built-in "labeled break" like some other languages. Common patterns to escape multiple levels:
Python 3.12 (WASM)# Pattern 1: use a flag
found = False
for i in range(5):
for j in range(5):
if i * j == 6:
found = True
break
if found:
break
# Pattern 2: extract the nested loops into a function and return
def find_pair(target):
for i in range(5):
for j in range(5):
if i * j == target:
return (i, j)
return None
result = find_pair(6)
The function-based approach is usually the cleanest and most readable.
6. Structural Pattern Matching (match)#
Introduced in Python 3.10, match provides a more expressive alternative
to long if/elif chains, especially for matching on structure, type, or
specific values.
Python 3.12 (WASM)def describe(value):
match value:
case 0:
return "zero"
case int() if value < 0:
return "negative integer"
case int():
return "positive integer"
case str():
return "a string"
case [first, *rest]:
return f"a list starting with {first}"
case {"name": name}:
return f"a dict with name {name}"
case _:
return "something else"
print(describe(0)) # zero
print(describe(-5)) # negative integer
print(describe("hi")) # a string
print(describe([1, 2, 3])) # a list starting with 1
print(describe({"name": "Al"})) # a dict with name Al
Matching multiple values with |#
Python 3.12 (WASM)def is_weekend(day):
match day:
case "Saturday" | "Sunday":
return True
case _:
return False
Matching class instances#
Python 3.12 (WASM)class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def locate(point):
match point:
case Point(x=0, y=0):
return "origin"
case Point(x=0, y=y):
return f"on the y-axis at {y}"
case Point(x=x, y=0):
return f"on the x-axis at {x}"
case Point():
return "somewhere else"
case _:
return "not a point"
The case _: branch acts as a catch-all, similar to else, and should
generally be included to handle unexpected values gracefully.
7. Common Pitfalls#
Modifying a list while iterating over it#
Python 3.12 (WASM)# Dangerous: mutating a list while looping over it skips elements
numbers = [1, 2, 3, 4, 5]
for n in numbers:
if n % 2 == 0:
numbers.remove(n) # unpredictable results!
# Safer: iterate over a copy, or build a new list
numbers = [1, 2, 3, 4, 5]
numbers = [n for n in numbers if n % 2 != 0]
Off-by-one errors with range#
Python 3.12 (WASM)# range(n) goes from 0 to n-1, NOT including n
for i in range(5):
print(i) # 0, 1, 2, 3, 4 — five values, not "up to 5"
Confusing elif chains with independent if statements#
Python 3.12 (WASM)score = 85
# Using separate if statements: ALL conditions are checked
if score >= 60:
print("Pass")
if score >= 80:
print("Distinction")
# both print — these are independent checks
# Using elif: only ONE branch runs
if score >= 80:
print("Distinction")
elif score >= 60:
print("Pass")
# only "Distinction" prints
Notes#
- Prefer
forloops when iterating over a known collection or range. - Prefer
whileloops when the number of iterations depends on a runtime condition rather than a fixed sequence. enumerate()andzip()are idiomatic Python — reaching for manual index tracking is usually a sign a built-in helper would be cleaner.- Overusing
break/continuein deeply nested loops can make control flow hard to follow; extracting logic into a function often helps. matchis not just aswitchstatement — it supports deep structural matching on lists, dicts, and class instances, whichif/elifcannot express as cleanly.- Python has no traditional
do-whileloop; it is typically simulated withwhile Trueplus an internalbreakcondition. - Never mutate a list (or other collection) while iterating over it directly — iterate over a copy or build a new collection instead.
Best Practices Checklist#
- Use
elifinstead of separateifstatements for mutually exclusive conditions - Prefer
foroverwhilewhen iterating a known sequence - Always ensure
whileloop conditions can become false - Use
enumerate()instead of manually tracking an index in aforloop - Use
zip()to iterate multiple sequences together instead of indexing into each separately - Reach for
matchwhen checking a value's type or structure across many cases - Keep nested loops shallow — extract inner logic into a function if nesting grows deep
- Use the loop
elseclause only when its "ran without break" meaning is clear to readers - Never modify a list while iterating over it directly
- Rely on truthiness checks (
if not items:) rather than explicit comparisons likeif len(items) == 0: