Control flow determines the execution order of statements. Without it, programs would be strictly sequential — no decisions, no repetition, no branching.
The Three Fundamental Structures#
Every algorithm can be expressed using only three structures (Böhm-Jacopini theorem):
flowchart LR
subgraph Sequence
A1["Statement A"] --> B1["Statement B"] --> C1["Statement C"]
end
subgraph Selection
D1{"Condition?"} -->|True| E1["Path A"]
D1 -->|False| F1["Path B"]
end
subgraph Iteration
G1{"Condition?"} -->|True| H1["Body"]
H1 --> G1
G1 -->|False| I1["Exit"]
endConditionals (Selection)#
If/Else#
The most basic decision structure:
def classify_temperature(celsius):
if celsius >= 40:
return "extreme heat"
elif celsius >= 30:
return "hot"
elif celsius >= 20:
return "comfortable"
elif celsius >= 10:
return "cool"
elif celsius >= 0:
return "cold"
else:
return "freezing"Guard Clauses#
Instead of deeply nested if/else, use early returns:
# BAD — deeply nested
def process_order(order):
if order is not None:
if order.is_valid():
if order.has_stock():
if order.payment_confirmed():
ship(order)
else:
raise PaymentError()
else:
raise StockError()
else:
raise ValidationError()
else:
raise ValueError("No order")
# GOOD — guard clauses (flat, readable)
def process_order(order):
if order is None:
raise ValueError("No order")
if not order.is_valid():
raise ValidationError()
if not order.has_stock():
raise StockError()
if not order.payment_confirmed():
raise PaymentError()
ship(order)Switch/Match (Multi-way Branching)#
When comparing one value against many options:
# Python 3.10+ match statement
def http_status_message(code):
match code:
case 200:
return "OK"
case 301:
return "Moved Permanently"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _ if 400 <= code < 500:
return "Client Error"
case _ if 500 <= code < 600:
return "Server Error"
case _:
return "Unknown"Ternary/Conditional Expressions#
Single-line conditionals for simple cases:
# Python
status = "active" if user.is_verified else "pending"
# JavaScript
const label = count === 1 ? "item" : "items";
# Java
String size = length > 100 ? "large" : "small";Rule: Use ternary only when both branches are simple expressions. If you need logic, use a full if/else.
Loops (Iteration)#
For Loops — Known Iterations#
Use when you know how many times to iterate (or you’re iterating over a collection):
# Iterate over a collection
for user in users:
send_notification(user)
# Iterate with index
for i, item in enumerate(items):
print(f"{i + 1}. {item}")
# Iterate a range
for i in range(0, 100, 5): # 0, 5, 10, ..., 95
print(i)
# Iterate over key-value pairs
for key, value in config.items():
print(f"{key} = {value}")While Loops — Unknown Iterations#
Use when you loop until a condition changes:
# Retry with backoff
def fetch_with_retry(url, max_retries=3):
attempt = 0
delay = 1
while attempt < max_retries:
response = requests.get(url)
if response.status_code == 200:
return response.json()
attempt += 1
time.sleep(delay)
delay *= 2 # exponential backoff
raise ConnectionError(f"Failed after {max_retries} attempts")Do-While (Execute at Least Once)#
// Java — guarantees at least one execution
Scanner scanner = new Scanner(System.in);
String input;
do {
System.out.print("Enter 'quit' to exit: ");
input = scanner.nextLine();
processInput(input);
} while (!input.equals("quit"));Python doesn’t have do-while, but you can emulate it:
while True:
input_val = input("Enter 'quit' to exit: ")
process_input(input_val)
if input_val == "quit":
breakLoop Control#
# break — exit the loop entirely
for item in items:
if item.is_critical_error():
log_error(item)
break # stop processing
# continue — skip to next iteration
for file in files:
if file.endswith(".tmp"):
continue # skip temp files
process(file)
# else on loops (Python) — runs if loop completed without break
for user in users:
if user.is_admin():
admin = user
break
else:
# No admin found — loop completed without break
raise NoAdminError("No admin user exists")Nested Control Flow#
Nested Loops#
# Matrix multiplication (O(n³))
def multiply_matrices(a, b):
rows_a, cols_a = len(a), len(a[0])
rows_b, cols_b = len(b), len(b[0])
result = [[0] * cols_b for _ in range(rows_a)]
for i in range(rows_a):
for j in range(cols_b):
for k in range(cols_a):
result[i][j] += a[i][k] * b[k][j]
return resultBreaking Out of Nested Loops#
# Using a flag
found = False
for row in matrix:
for cell in row:
if cell == target:
found = True
break
if found:
break
# Better: extract to a function
def find_in_matrix(matrix, target):
for i, row in enumerate(matrix):
for j, cell in enumerate(row):
if cell == target:
return (i, j)
return NoneHypothetical Use Cases#
Use Case 1: Traffic Light Controller#
class TrafficLight:
def __init__(self):
self.state = "red"
self.timer = 0
def tick(self):
"""Called every second."""
self.timer += 1
match self.state:
case "red":
if self.timer >= 60:
self.state = "green"
self.timer = 0
case "green":
if self.timer >= 45:
self.state = "yellow"
self.timer = 0
case "yellow":
if self.timer >= 5:
self.state = "red"
self.timer = 0stateDiagram-v2
[*] --> Red
Red --> Green: 60 seconds
Green --> Yellow: 45 seconds
Yellow --> Red: 5 secondsUse Case 2: Input Validation Pipeline#
def validate_registration(data):
"""Validate user registration with multiple rules."""
errors = []
# Required fields
for field in ["email", "password", "name"]:
if field not in data or not data[field].strip():
errors.append(f"{field} is required")
if errors:
return {"valid": False, "errors": errors}
# Email format
if "@" not in data["email"] or "." not in data["email"].split("@")[1]:
errors.append("Invalid email format")
# Password strength
password = data["password"]
if len(password) < 8:
errors.append("Password must be at least 8 characters")
if not any(c.isupper() for c in password):
errors.append("Password must contain an uppercase letter")
if not any(c.isdigit() for c in password):
errors.append("Password must contain a digit")
return {"valid": len(errors) == 0, "errors": errors}Use Case 3: Pagination Iterator#
def fetch_all_pages(api_url, page_size=100):
"""Fetch all pages from a paginated API."""
all_items = []
page = 1
while True:
response = requests.get(api_url, params={"page": page, "size": page_size})
data = response.json()
items = data["items"]
all_items.extend(items)
# Exit conditions
if len(items) < page_size: # last page (partial)
break
if page >= data.get("total_pages", float("inf")):
break
page += 1
return all_itemsUse Case 4: Game Loop#
def game_loop():
"""Main game loop — runs until player quits."""
game_state = initialize_game()
clock = pygame.time.Clock()
while game_state.running:
# 1. Handle input
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_state.running = False
continue
handle_input(game_state, event)
# 2. Update state
game_state.update(clock.get_time())
# 3. Check win/lose conditions
if game_state.player.health <= 0:
show_game_over()
break
if game_state.objectives_complete():
show_victory()
break
# 4. Render
render(game_state)
clock.tick(60) # 60 FPS capCommon Pitfalls#
1. Off-by-One Errors#
# BAD — misses last element
for i in range(len(items) - 1): # should be range(len(items))
process(items[i])
# BAD — index out of bounds
for i in range(1, len(items) + 1): # items[len(items)] doesn't exist
process(items[i])2. Infinite Loops#
# BAD — condition never becomes false
i = 10
while i > 0:
print(i)
# forgot: i -= 1
# BAD — floating point comparison
x = 0.0
while x != 1.0: # may never be exactly 1.0
x += 0.13. Modifying Collection During Iteration#
# BAD — modifying list while iterating
for item in items:
if item.is_expired():
items.remove(item) # skips elements!
# GOOD — filter to new list
items = [item for item in items if not item.is_expired()]
# GOOD — iterate over copy
for item in items[:]: # slice creates a copy
if item.is_expired():
items.remove(item)4. Short-Circuit Evaluation Gotchas#
# AND short-circuits: if first is False, second is never evaluated
if user is not None and user.is_active(): # safe — won't call .is_active() on None
...
# OR short-circuits: if first is True, second is never evaluated
result = cached_value or expensive_computation() # only computes if cache is emptyKey Takeaways#
- Sequence, selection, iteration — these three structures can express any algorithm
- Guard clauses over deep nesting — flat code is readable code
- Choose the right loop —
forwhen you know iterations,whilewhen you don’t - Always ensure termination — every loop needs a condition that will eventually be false
- Avoid modifying collections during iteration — use filters or copies instead