Control Flow: Making Decisions & Repeating
if, for and while change that: they let a program choose, and repeat. Three keywords. That is genuinely the whole of it, and by the end of this lesson you will have written all three. Control flow is the spine of every program you'll ever write.After this lesson, you will be able to:
- Write if/elif/else statements to make your programs respond to different conditions
- Use for loops to iterate over sequences and range() to generate number sequences
- Use while loops for indefinite repetition with a stopping condition
- Control loop execution with break (exit early) and continue (skip iteration)
- Combine conditions, loops, and logic to solve the classic FizzBuzz problem
- Use a shortcut for assigning and checking values at the same time (walrus operator — optional, for later)
- Apply match/case structural pattern matching for elegant dispatch (Python 3.10+)
Before You Start
#If/Elif/Else — Making Decisions
if statement lets your program make decisions based on conditions:temperature = 35
if temperature > 30:
print("It is hot outside! Stay hydrated.")You just wrote your first decision! Your program now does different things depending on what it finds. You're already thinking like a programmer.
temperature > 30 is True, the indented code runs. If it is False, Python skips it entirely.#The Full Pattern: if / elif / else
score = 85
if score >= 90:
grade = "A"
print("Excellent work!")
elif score >= 80:
grade = "B"
print("Good job!")
elif score >= 70:
grade = "C"
print("Not bad, keep studying.")
elif score >= 60:
grade = "D"
print("You need to study more.")
else:
grade = "F"
print("Let's talk to your teacher.")
print(f"Your grade: {grade}")Key rules
ifis required and comes firstelif(short for "else if") is optional — you can have as many as you wantelseis optional and comes last — it catches everything that did not match above- Indentation matters! Python uses indentation (4 spaces) instead of curly braces to define code blocks. This is non-negotiable
Hit anIndentationErrororTabError? Mixing tabs and spaces, or forgetting to indent afterif:/for:, is the cause. See the error decoder for the fix.
What does this print? x = 5 if x > 10: print("big") elif x > 3: print("medium") elif x > 0: print("small")
#Nesting and Combining Conditions
age = 16
has_permit = True
has_glasses = False
# Combining with and/or
if age >= 16 and has_permit:
print("You can drive!")
# Nesting (if inside if)
if age >= 13:
print("You can create a social media account.")
if age >= 16:
print("You can also drive with a permit.")
if age >= 18:
print("You can vote!")
# Ternary (one-line if/else)
status = "adult" if age >= 18 else "minor"
print(f"Status: {status}")Optional shortcut: The ternary form below shows a compact way to write simple if/else. If it feels confusing, skip it for now — you can always come back to it later.
value_if_true if condition else value_if_false is great for simple decisions. Use the full if/else block for anything complex.#For Loops — Repeating a Known Number of Times
for loop runs a block of code once for each item in a sequence:# Loop through 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#The range() Function
range() generates a sequence of numbers. It is the most common way to run a loop a specific number of times:# range(5) generates: 0, 1, 2, 3, 4
for i in range(5):
print(f"Iteration {i}")
# range(start, stop) -- from start up to (but not including) stop
for i in range(1, 6):
print(i) # 1, 2, 3, 4, 5
# range(start, stop, step) -- with a step size
for i in range(0, 20, 5):
print(i) # 0, 5, 10, 15
# Count backwards
for i in range(10, 0, -1):
print(i) # 10, 9, 8, ... 1range(5) starts at 0 and goes up to (but does not include) 5. This "up to but not including" pattern appears everywhere in Python.How many times does for i in range(5) run?
range(5) starts at 0 and stops before 5. This zero-indexing and exclusive-end pattern is consistent throughout Python (and most programming languages).#Step 1: The Loop Begins
for i in range(5): and generates the sequence [0, 1, 2, 3, 4]. It sets i = 0 and enters the loop body for the first time.for i in range(5): # i = 0
print(f"Step {i}: i squared is {i ** 2}")
# Output: Step 0: i squared is 0#Step 2: Second Iteration
i = 1 and runs the loop body again. You do not need to manually increment i — the for loop handles it.for i in range(5): # i = 1
print(f"Step {i}: i squared is {i ** 2}")
# Output: Step 1: i squared is 1#Step 3: Third Iteration
i = 2 now. The expression i ** 2 evaluates to 4. Each iteration, the loop body executes with the new value of i.for i in range(5): # i = 2
print(f"Step {i}: i squared is {i ** 2}")
# Output: Step 2: i squared is 4#Step 4: Fourth and Fifth Iterations
i = 3 gives 9, i = 4 gives 16. After i = 4, there are no more values in the range, so the loop ends and Python moves to the next line after the loop.# Output: Step 3: i squared is 9
# Output: Step 4: i squared is 16
# Loop is done -- Python continues below#The Full Picture
Five iterations, five outputs. The for loop is deterministic — you know exactly how many times it will run before it starts. This is what makes it perfect for iterating over the rows of a file, the items in a list, or the days in a month.
Step 0: i squared is 0
Step 1: i squared is 1
Step 2: i squared is 4
Step 3: i squared is 9
Step 4: i squared is 16
#Looping Through Strings
Strings are sequences of characters, so you can loop through them:
word = "PYTHON"
for letter in word:
print(letter, end=" ")
# Output: P Y T H O N#enumerate() — Get Index and Value
Often you need both the index and the value:
colors = ["red", "green", "blue"]
for index, color in enumerate(colors):
print(f"{index}: {color}")
# 0: red
# 1: green
# 2: blueWhat for actually does behind your back
for x in xs: line is sugar. Under the hood Python calls iter(xs) to get an iterator object, then calls next(it) in a loop until it sees StopIteration. The animation below opens up that mechanism — watch the iterator gear advance its pointer and StopIteration end the loop. Then try the four other presets (exhausted iterator, generators, infinite sources, zip) to see why this protocol is everywhere in Python.#Now trace YOUR own loop
The animation above shows a canned 5-iteration loop. Now try it yourself: edit the code below, click Trace it, and watch Python execute line-by-line. Add a variable. Change the range. Introduce a bug. The trace updates with whatever you write.
Edit the code, then click Trace it. Python actually runs in your browser — every line, every variable, every print.
Click Trace it to capture the execution trace. The scrubber below will let you step through every variable change line-by-line.
#While Loops — Repeating Until a Condition Changes
while loop runs as long as a condition is True:count = 0
while count < 5:
print(f"Count is {count}")
count += 1 # DO NOT FORGET THIS or you get an infinite loop!
print("Done!")#When to Use While vs For
forloop: When you know how many times to repeat (iterate over a list, run 100 times)whileloop: When you do not know how many times — you repeat until something changes (keep asking for input until it is valid, train until loss is low enough)
This code calls input(). Python here can't pause to ask you, so type the answers below before running — one value per line, in the order the program asks for them.
# Real-world while loop: input validation
password = ""
while password != "python123":
password = input("Enter the password: ")
if password != "python123":
print("Wrong! Try again.")
print("Access granted!")#The Infinite Loop Trap
False, your loop runs forever:# DANGER: infinite loop!
# x = 1
# while x > 0:
# print(x)
# x += 1 # x keeps growing, always > 0
# To stop an infinite loop: press Ctrl+C in the terminalFalse. This usually means updating a variable inside the loop.#Break and Continue
break — Exit the Loop Immediately
# Find the first number divisible by 7
for i in range(1, 100):
if i % 7 == 0:
print(f"Found it: {i}")
break # stop the loop, do not check the rest
# Output: Found it: 7What is the final value of total? total = 0 for i in range(1, 5): total = total + i print(total)
i = 1, 2, 3, 4. After each iteration, total accumulates: 0 + 1 = 1, 1 + 2 = 3, 3 + 3 = 6, 6 + 4 = 10. This is the classic "accumulator" pattern — every running sum, every average, every count works this way. Note that range(1, 5) stops BEFORE 5, so we never add 5 (a common off-by-one error would give 15).continue — Skip to the Next Iteration
# Print only odd numbers
for i in range(10):
if i % 2 == 0:
continue # skip even numbers
print(i)
# Output: 1, 3, 5, 7, 9break in a While Loop
This code calls input(). Python here can't pause to ask you, so type the answers below before running — one value per line, in the order the program asks for them.
# A simple guessing game
import random
secret = random.randint(1, 20)
attempts = 0
while True: # loop forever (until we break)
guess = int(input("Guess a number 1-20: "))
attempts += 1
if guess < secret:
print("Too low!")
elif guess > secret:
print("Too high!")
else:
print(f"You got it in {attempts} attempts!")
break # exit the loopwhile True pattern with a break inside is very common. It means "loop forever until I explicitly say stop."What does `break` do inside a for loop?
#Nested Loops
You can put loops inside loops:
# Multiplication table
for i in range(1, 6):
for j in range(1, 6):
print(f"{i * j:4}", end="")
print() # new line after each row
# 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 25Nested loops are how you process anything laid out in a grid — the rows and columns of a spreadsheet, the squares of a chessboard, the pixels of an image.
#FizzBuzz — The Classic Exercise
FizzBuzz is the most famous programming exercise. The rules are:
- Print numbers 1 through 20
- If divisible by 3, print "Fizz" instead
- If divisible by 5, print "Buzz" instead
- If divisible by both 3 and 5, print "FizzBuzz"
for i in range(1, 21):
if i % 3 == 0 and i % 5 == 0:
print("FizzBuzz")
elif i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
else:
print(i)for, range(), if/elif/else, % (modulo), and and. If you understand every line, you have a solid grasp of control flow.Tests · Try each challenge. For challenge 5, the answer should be 154 (7 * 11 * 2).
This program is supposed to print the numbers 1 through 10 inclusive, then a total. But the last number is missing and the total is wrong. Fix the range.
1 2 3 4 5 6 7 8 9 10 Total: 55
Build FizzBuzz from 1 to 30
Print every number from 1 to 30. Replace multiples of 3 with 'Fizz', multiples of 5 with 'Buzz', and multiples of both with 'FizzBuzz'.
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
... (continues to 30)# Print FizzBuzz from 1 to 30
for i in range(1, 31):
# TODO: if divisible by 3 AND 5 → "FizzBuzz"
# TODO: elif divisible by 3 → "Fizz"
# TODO: elif divisible by 5 → "Buzz"
# TODO: else → print the number
pass#Real-World Examples
Control flow is not just for toy problems. Here is how it shows up in real programs:
#Retrying Until Something Works
# Trying to connect to something that is sometimes slow.
# Each attempt waits twice as long as the last one.
max_attempts = 6
wait = 1.0 # seconds
for attempt in range(max_attempts):
# Pretend the connection gets more likely to succeed each time
connected = attempt >= 3
if connected:
print(f"Attempt {attempt}: connected. Stopping early.")
break
print(f"Attempt {attempt}: failed, waiting {wait} seconds")
wait = wait * 2
else:
print("Gave up after all attempts")#Turning a Score Into a Decision
# A spam filter has scored an email from 0 to 1.
# Now something has to decide what to DO with that score.
probability = 0.87
if probability > 0.9:
prediction = "definitely spam"
confidence = "high"
elif probability > 0.5:
prediction = "probably spam"
confidence = "medium"
else:
prediction = "not spam"
confidence = "high" if probability < 0.1 else "medium"
print(f"Prediction: {prediction} (confidence: {confidence})")#Data Filtering
# Filter out invalid data points
raw_data = [23, -1, 45, None, 67, 0, 89, -5, 100]
clean_data = []
for value in raw_data:
if value is None:
continue # skip missing values
if value < 0:
continue # skip negative values (invalid for this dataset)
clean_data.append(value)
print(f"Cleaned: {clean_data}") # [23, 45, 67, 0, 89, 100]
print(f"Kept {len(clean_data)} of {len(raw_data)} data points")#The Walrus Operator := (Python 3.8+)
:=) assigns AND returns a value in one expression — perfect for avoiding repetition in conditions:# Without walrus — compute twice
import re
data = "User age: 25"
if re.search(r'\d+', data):
match = re.search(r'\d+', data) # called twice!
print(f"Found: {match.group()}")
# With walrus — compute once
if match := re.search(r'\d+', data):
print(f"Found: {match.group()}") # match is available hereThe walrus operator is most useful in while loops and comprehensions:
# Classic pattern: read until empty
import sys
while line := sys.stdin.readline():
process(line)
# In comprehension — filter and transform without double call
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Without walrus — expensive function called twice
results = [expensive(x) for x in numbers if expensive(x) > 5]
# With walrus — call once, use twice
results = [y for x in numbers if (y := expensive(x)) > 5]#match/case: Structural Pattern Matching (Python 3.10+)
match/case, which is much more powerful than a simple switch statement:# Basic value matching
command = "quit"
match command:
case "quit":
print("Exiting...")
case "help":
print("Available commands: quit, help, run")
case "run":
print("Running...")
case _: # _ is the wildcard (catches everything else)
print(f"Unknown command: {command}")#Pattern Matching with Types and Structures
# Match on type and destructure at the same time
def process_event(event):
match event:
case {"type": "click", "x": x, "y": y}:
print(f"Click at ({x}, {y})")
case {"type": "keypress", "key": key}:
print(f"Key pressed: {key}")
case {"type": "resize", "width": w, "height": h}:
print(f"Resized to {w}x{h}")
case _:
print("Unknown event")
process_event({"type": "click", "x": 100, "y": 200})
# Output: Click at (100, 200)
# Match sequences
def describe_list(lst):
match lst:
case []:
return "empty"
case [x]:
return f"single item: {x}"
case [x, y]:
return f"two items: {x} and {y}"
case [first, *rest]:
return f"starts with {first}, then {len(rest)} more"
# Match with guards (if conditions)
point = (3, -1)
match point:
case (x, y) if x == y:
print(f"Point on diagonal: ({x}, {y})")
case (x, y) if x > 0 and y > 0:
print(f"Point in quadrant I: ({x}, {y})")
case (x, y):
print(f"Point at ({x}, {y})")Match/case is more powerful than if/elif chains when you need to destructure data. It is especially useful for parsing JSON-like structures — JSON, JavaScript Object Notation, being the text format most web services reply in — handling API (application programming interface) responses, and implementing command parsers.
*rest. Watch how each case is tested in order, how name captures (x, y, r, args) fly out as bindings, and how guards run only AFTER the structural match succeeds. The wildcard _ only fires when nothing else matched.#Key Takeaways
if/elif/elselets your program make decisions — check a condition, and run different code depending on whether it isTrueorFalse. Indentation defines the code blocksforloops iterate over sequences — userange(n)for a specific count, or loop directly over lists, strings, and other sequences.range(5)gives0, 1, 2, 3, 4whileloops repeat until a condition changes — use when you do not know how many iterations you need. Always ensure the condition will eventually becomeFalsebreakexits a loop early,continueskips to the next iteration — these give you fine-grained control over loop execution- Control flow is the backbone of every program — validating input, filtering a list, retrying a failure, and stopping early all rely on these building blocks
What is the output of: for i in range(3): print(i)