Python Module 3 – Control flow: decisions, loops and pattern matching
Decisions with if, elif and else
Write if, elif and else chains that cover every case, choose between two values in one line, and avoid the classic x == 1 or 2 mistake in Python.
What you will learn
- Write if/elif/else chains that cover every case
- Use conditional expressions for simple choices
- Avoid mistakes such as if x == 1 or 2
Before you start
On this page
Almost every useful program has to choose: charge one price or another, accept an answer or ask again, print a
warning or stay quiet. Python’s if statement runs a block of code only when a condition is true. Add elif and
else and you can describe several cases, of which Python runs exactly one.
The parts of an if statement
An if statement starts with the word if, a condition and a colon. The lines under it that are indented further
form its block, and they run only when the condition is true. An else: line at the same indentation as the if
starts a second block, for when the condition is false:
temperature = 41
if temperature > 40:
print("Heat warning")
print("Carry water")
else:
print("A normal day")
print("Forecast done") Output
Heat warning Carry water Forecast done
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 heat_warning.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The temperature is above 40, so both indented lines under if ran and the else block was skipped. The last line is
not indented at all, so it is not part of either block: it runs whatever the temperature is. Change 41 to 25 with
Edit and run it again to see the other path.
Three rules make this work:
- The indentation is the block. Python has no braces or
endkeywords; a block lasts until a line that is indented less. PEP 8, Python’s style guide, asks for four spaces per level. - A block cannot be empty. When you have nothing to put in one yet, write
pass, a statement that does nothing, or..., which people often use the same way. - Tabs and spaces do not mix. If some lines are indented with tabs and others with spaces in a way whose meaning
depends on how wide a tab is, Python stops with a
TabError. Let your editor indent with spaces only.
Forget to indent, and the program does not run at all:
temperature = 41
if temperature > 40:
print("Heat warning") Output (exit status 1)
Printed as an error (standard error)
File "missing_block.py", line 3
print("Heat warning")
^^^^^
IndentationError: expected an indented block after 'if' statement on line 2
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 missing_block.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The message names the line of the if that is missing its block. Like any syntax error, it stops the whole file before
the first line runs, so not even the assignment on line 1 happened.
Any value can be a condition
A condition is usually a comparison such as temperature > 40, which is either True or False. But if accepts
any value and asks whether it counts as true. False, None, zero (0, 0.0) and empty strings, lists and other
empty containers count as false; every other value counts as true. So if name: means “if name is not an empty
string”, a short form you will see often. Keep this rule in mind: it explains the classic if mistake shown in the
section on x == 1 or 2 below.
Several cases with elif
elif, short for “else if”, adds more conditions between the if and the else. Python tests them from the top,
runs the block of the first one that is true, and skips the rest of the chain without even testing it. When no
condition is true, the else block runs, if there is one.
Suppose electricity is billed in slabs, where each band of units has its own rate. The function below works out a month’s charge with made-up rates: 3 for each of the first 100 units, 5 for each of the next 200 and 8 for every unit above 300.
def and return
The examples from here on put the decision inside a function, so that the program can try it with several inputs:
def energy_charge(units): gives the block a name, and return hands back the result and ends the function at once.
Functions get a module of their own later in this track.
# A month's energy charge, worked out slab by slab: the first 100
# units cost 3 each, the next 200 cost 5 each, and every unit above
# 300 costs 8. The rates are made up for this example.
def energy_charge(units):
if units <= 100:
return units * 3
elif units <= 300:
return 100 * 3 + (units - 100) * 5
else:
return 100 * 3 + 200 * 5 + (units - 300) * 8
print("80 units:", energy_charge(80))
print("100 units:", energy_charge(100))
print("101 units:", energy_charge(101))
print("300 units:", energy_charge(300))
print("301 units:", energy_charge(301))
print("450 units:", energy_charge(450)) Output
80 units: 240 100 units: 300 101 units: 305 300 units: 1300 301 units: 1308 450 units: 2500
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 slab_bill.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
How the if, elif and else chain of slab_bill.py picks one block
Text description of the diagram
The diagram is a flowchart of energy_charge(units), read from the top.
- Python first tests units <= 100. When it is true, the if block runs (units × 3) and nothing else in the chain is tested.
- When it is false, Python tests units <= 300. When that is true, the elif block runs: 300 + (units − 100) × 5.
- When both tests are false, the else block runs: 1300 + (units − 300) × 8.
Each path ends in exactly one block, so every number of units gets exactly one of the three formulas.
Read the chain the way Python does. For 450 units, units <= 100 is false, units <= 300 is false, so the else
block runs. For 250 units the second test is true, and Python never looks at the else. The elif needs no lower
limit: it is tested only after units <= 100 was false, so every value that reaches it is already above 100. Written
on its own, the same test would be the chained comparison 100 < units <= 300.
The program tries 100 and 101, and 300 and 301, on purpose. Mistakes in chains like this hide at the boundaries,
where < and <= give different answers, so test the value on each edge and the one just past it: at 301 units,
exactly one unit is charged at the top rate, so the charge is 1300 + 8. And because the chain ends with else, every
number of units gets a charge: no case falls through the gaps.
A common mistake: tests in the wrong order
The order of the tests matters as much as the tests themselves. Here the two conditions of the same chain are swapped:
# The same slabs with the two tests swapped: the wider one is first.
def energy_charge(units):
if units <= 300:
return 100 * 3 + (units - 100) * 5
elif units <= 100:
return units * 3
else:
return 100 * 3 + 200 * 5 + (units - 300) * 8
print("80 units:", energy_charge(80))
print("250 units:", energy_charge(250)) Output
80 units: 200 250 units: 1050
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 wrong_order.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
80 units now cost 200 instead of 240. units <= 300 is also true for 80, so the second slab’s formula runs, and the
elif units <= 100 block can never run for any number. Put the narrowest test first, or give every test both of its
limits so that the order no longer matters.
The x == 1 or 2 mistake
Here is a check for weekend prices that looks right and is not:
day = "Tue"
if day == "Sat" or "Sun":
print("Weekend price")
else:
print("Weekday price")
print(day == "Sat" or "Sun")
if day == "Sat" or day == "Sun":
print("Weekend price")
else:
print("Weekday price")
if day in ("Sat", "Sun"):
print("Weekend price")
else:
print("Weekday price") Output
Weekend price Sun Weekday price Weekday price
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 weekend_bug.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
On a Tuesday the first if still printed “Weekend price”. Python reads day == "Sat" or "Sun" as
(day == "Sat") or "Sun", because == is applied before or. Then or works like this: if its left side counts as
true, the result is the left side; otherwise the result is the right side. day == "Sat" is False, so the whole
condition is the string "Sun", as the second line of output shows, and a non-empty string counts as true. The
if block runs on every day of the week.
The last two tests in the program are the fixes. Either compare the variable each time,
day == "Sat" or day == "Sun", or ask whether it is one of several values with in:
day in ("Sat", "Sun"). The second form stays short when the list grows. The same trap catches numbers:
if x == 1 or 2: is always true, while if x in (1, 2): is what was meant.
A choice in one line: conditional expressions
When all you need is one of two values, a conditional expression does it inside a single line:
value_if_true if condition else value_if_false.
age = 16
label = "adult" if age >= 18 else "minor"
print(age, label)
tickets = 1
print(tickets, "ticket" if tickets == 1 else "tickets")
tickets = 3
print(tickets, "ticket" if tickets == 1 else "tickets")
# Only the chosen side is worked out, so no division happens here.
total = 0
count = 0
average = total / count if count > 0 else 0
print("Average:", average) Output
16 minor 1 ticket 3 tickets Average: 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 one_line_choice.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Python works out the condition first and then only the side it chose. That is why the last example is safe: count
is 0, so total / count is never calculated and there is no ZeroDivisionError. Use conditional expressions for short
choices between two values, like a label or a word that depends on a number. When each case does something, when there
are more than two cases, or when you are tempted to put one conditional expression inside another, write an
if statement instead: it is easier to read and to change.
Flatter code with guard clauses
Conditions inside conditions push the main work further and further to the right. Both versions of withdraw below
give the same answers; compare how they read:
Nested · withdraw_nested.py
def withdraw(balance, amount):
if amount > 0:
if amount <= balance:
return f"Paid {amount}, {balance - amount} left"
else:
return "Not enough money"
else:
return "The amount must be more than 0"
print(withdraw(500, 200))
print(withdraw(500, 900))
print(withdraw(500, -5)) Output
Paid 200, 300 left Not enough money The amount must be more than 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 withdraw_nested.py
Guard clauses · withdraw_guard.py
def withdraw(balance, amount):
if amount <= 0:
return "The amount must be more than 0"
if amount > balance:
return "Not enough money"
return f"Paid {amount}, {balance - amount} left"
print(withdraw(500, 200))
print(withdraw(500, 900))
print(withdraw(500, -5)) Output
Paid 200, 300 left Not enough money The amount must be more than 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 withdraw_guard.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The second version checks for each problem first and returns straight away. Because return ends the function, the
cases that get past both checks need no else, and the normal case sits at the bottom, not indented any further.
These early checks are called guard clauses. They keep each rule on its own two lines, so adding a third rule (a
daily limit, say) means adding two lines rather than another level of nesting.
Python 3.14: a clearer message for elif after else
An else block must be the last part of its chain. Put an elif after it and Python refuses the file:
units = 120
if units <= 100:
print("First slab")
else:
print("Above 100 units")
elif units <= 300:
print("Second slab") Output (exit status 1)
Printed as an error (standard error)
File "elif_after_else.py", line 6
elif units <= 300:
^^^^
SyntaxError: 'elif' block follows an 'else' block
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 elif_after_else.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Version note
New in Python 3.14: this mistake gets its own message, 'elif' block follows an 'else' block. Python 3.13 and older
report it only as invalid syntax. To fix it, move the else block to the end of the chain.
When you compare one value with many fixed values, such as commands typed by a user, the match statement from a
later lesson of this module can be easier to read than a long elif chain.
Key takeaways
- An
ifblock is the indented lines after the colon; four spaces per level, never a mix of tabs and spaces. Writepasswhen a block has nothing to do yet. - In an
if/elif/elsechain Python runs only the first block whose condition is true; finish withelseso that every case is covered, and test the values on each boundary. - Order the tests from the narrowest to the widest, or give each test both of its limits.
x == 1 or 2is always true; writex == 1 or x == 2, orx in (1, 2).a if condition else bpicks one of two values and works out only the side it picks.- Guard clauses (check, then return early) keep functions flat and easy to extend.
Exercise
Exercise · Easy · Python
Work out the price of a cinema ticket
A small cinema prices its tickets by age, gives students a discount and charges more at weekends. Write ticket_price(age, is_student, day), which returns the price of one ticket as a whole number.
ageis a whole number of years, 0 or more;is_studentisTrueorFalse;dayis one of"Mon","Tue","Wed","Thu","Fri","Sat"and"Sun".- Children under 3 go free: their ticket is 0 on every day.
- Children from 3 to 12 pay 120.
- Visitors aged 60 or more pay 160.
- Students aged 13 to 59 pay 180.
- Everyone else pays 240.
- On
"Sat"and"Sun", every ticket that is not free costs 40 more.
A student who is also a child or a senior pays the child or senior price, so ticket_price(10, True, "Mon") is 120 and ticket_price(65, True, "Mon") is 160, while ticket_price(25, True, "Sun") is 220.
Starter code · tickets.py
def ticket_price(age, is_student, day):
"""Return the price of one ticket (see the rules in the prompt)."""
price = 240
return price The sample tests · test_tickets.py
from tickets import ticket_price
def test_children():
"""children under 3 go free and children from 3 to 12 pay 120"""
assert ticket_price(0, False, "Mon") == 0
assert ticket_price(2, False, "Wed") == 0
assert ticket_price(3, False, "Mon") == 120
assert ticket_price(12, False, "Thu") == 120
def test_adults_and_seniors():
"""adults pay 240 and visitors aged 60 or more pay 160"""
assert ticket_price(13, False, "Mon") == 240
assert ticket_price(59, False, "Fri") == 240
assert ticket_price(60, False, "Fri") == 160
assert ticket_price(85, False, "Tue") == 160
def test_students():
"""students from 13 to 59 pay 180, others the child or senior price"""
assert ticket_price(13, True, "Mon") == 180
assert ticket_price(59, True, "Thu") == 180
assert ticket_price(10, True, "Mon") == 120
assert ticket_price(65, True, "Mon") == 160
def test_weekend():
"""Saturday and Sunday tickets cost 40 more, except free ones"""
assert ticket_price(30, False, "Sat") == 280
assert ticket_price(25, True, "Sun") == 220
assert ticket_price(8, False, "Sun") == 160
assert ticket_price(70, False, "Sat") == 200
assert ticket_price(1, False, "Sat") == 0
def test_weekdays():
"""no weekday costs extra"""
for day in ("Mon", "Tue", "Wed", "Thu", "Fri"):
assert ticket_price(30, False, day) == 240 A hint
Handle the free tickets first and return 0 straight away. Then pick the base price with one if/elif/else chain, testing the ages in order (once age <= 12 has been ruled out, the next tests only see visitors of 13 or more), and add the weekend charge afterwards with a separate if day in ("Sat", "Sun"):, so that it is written only once.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
References
- The Python Tutorial: if Statements (Python Software Foundation)
- The Python Language Reference: The if statement (Python Software Foundation)
- The Python Language Reference: Boolean operations (Python Software Foundation)
- The Python Language Reference: Operator precedence (Python Software Foundation)
- The Python Language Reference: Conditional expressions (Python Software Foundation)
- The Python Language Reference: Indentation (Python Software Foundation)
- The Python Tutorial: pass Statements (Python Software Foundation)
- PEP 8: Style Guide for Python Code (Python Software Foundation)
- What's New In Python 3.14: improved error messages (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress