Skip to main content

Python Control Flow: Master Conditional Statements and Loops

Calculating read time…

In the previous lesson, you learned Python's core data types — strings, integers, floats, booleans, and None. Now it's time to make your programs actually think.

Imagine you're baking a cake. You check the oven temperature, decide if the batter is mixed enough, and choose the right icing. Every step is a decision.

In Python, control flow is exactly that — a way to tell your code how to make decisions and choose what to do next. Today, we'll master two superhero tools: if…else and match.

By the end, you'll write code that thinks, chooses, and acts — just like you do!

💡 What You'll Master Today:
  • How if, elif, and else let your code make decisions
  • Nested if statements for hierarchical logic
  • Python's modern match statement (Python 3.10+)
  • Advanced match features: pattern unpacking and guard clauses
  • When to use if...elif versus match
  • A complete hands-on project: a Food Order Billing System

🚦 1. What is Control Flow? The Traffic Cop of Your Code

Control flow is the order in which your program runs its instructions. Without it, code runs top-to-bottom, like a robot with no brain.

With control flow, your code becomes smart. It can react, decide, and adapt.

💡 DO: Think of Control Flow Like a Choose-Your-Own-Adventure Book
Each page asks: "Go left?" or "Fight the dragon?" Your choice changes the story. Python's if and match are your way of writing those choices into code.

🔀 2. Python If…Else: Your First Decision-Maker

The if statement is the most basic form of decision-making. It checks a condition: if it's true, it runs a block of code. If not, it skips it.

The Simple "If": A One-Way Check

Let's start with checking if a student passed an exam.

📌 What this code does: compares score to passing_marks. Since 75 is greater than 40, the condition is True, so the indented print() line underneath runs.
score = 75
passing_marks = 40

if score > passing_marks:
    print("Congratulations! You passed the exam!")

Output:

Congratulations! You passed the exam!

See what happened? The condition (score > passing_marks) was True (75 is greater than 40), so Python ran the indented print statement.

⚠️ IMPORTANT: Indentation is King!
In Python, the spaces or tabs at the start of a line define a code block. After the if line, you must indent the next line. Usually, we use 4 spaces. Your editor will often do this for you!

Adding "Else": The Other Option

What if the student failed? We need a backup plan. That's where else comes in.

📌 What this code does: this time score (35) is not greater than passing_marks (40), so the if condition is False and Python runs the else block instead.
score = 35
passing_marks = 40

if score > passing_marks:
    print("Congratulations! You passed the exam!")
else:
    print("Sorry, you did not pass. Try again next time.")

Output:

Sorry, you did not pass. Try again next time.

Now our program handles both possibilities. It's a simple but powerful fork in the road.

Introducing "Elif": The Middle Ground

Life isn't just pass/fail. What about grades like A, B, C? We need more branches. Enter elif (short for "else if").

📌 What this code does: checks score against five ranges, top to bottom. Since 82 fails the first check (>= 90) but passes the second (>= 75), Python runs that block and skips every condition below it.
score = 82

if score >= 90:
    print("Grade: A - Outstanding!")
elif score >= 75:
    print("Grade: B - Great job!")
elif score >= 60:
    print("Grade: C - Good effort.")
elif score >= 40:
    print("Grade: D - You passed, but aim higher.")
else:
    print("Grade: F - Please retake the exam.")

Output:

Grade: B - Great job!

Python checks each condition in order. The first one that is True wins, and it runs that block. The rest are ignored.

🚫 DON'T: Forget the Order of Your Conditions
Always write your conditions from most specific to least specific. If you write if score >= 40: first, it will be True for a score of 95, and the "A" grade block will never run!

🌡️ 3. Real-World Example: Smart Home Thermostat

Let's build a practical program. Imagine a thermostat that decides whether to turn the heater, AC, or fan on.

📌 What this code does: compares the current temperature against a comfortable range (20°C–24°C) and decides between three actions — heater, AC, or fan — storing the decision and the reason in variables before printing both.
current_temp = 22  # in Celsius
ideal_min = 20
ideal_max = 24

print(f"Current room temperature: {current_temp}°C")

if current_temp < ideal_min:
    action = "Turning HEATER ON."
    reason = f"Too cold (below {ideal_min}°C)."
elif current_temp > ideal_max:
    action = "Turning AC ON."
    reason = f"Too hot (above {ideal_max}°C)."
else:
    action = "Turning FAN ON for circulation."
    reason = "Temperature is comfortable."

print(f"Decision: {action}")
print(f"Reason: {reason}")

Output:

Current room temperature: 22°C
Decision: Turning FAN ON for circulation.
Reason: Temperature is comfortable.

This is a clear, multi-step decision tree. Your code is now making logical choices like a real appliance!

🪆 4. Nested If Statements: Decisions Within Decisions

Sometimes you need to check a second condition only after the first is true. This is called nesting.

Example: A movie ticket booking system that checks age, then applies a student discount.

📌 What this code does: first checks if the customer is under 18. Since age is 19, that's False, so Python moves into the else branch — where a second, nested if checks student status and applies a 25% discount.
age = 19
is_student = True
ticket_price = 12.50

if age < 18:
    ticket_price = ticket_price * 0.5  # 50% child discount
    print(f"Child discount applied. Ticket price: ${ticket_price}")
else:
    # This "else" block is for adults (age >=18)
    if is_student:
        ticket_price = ticket_price * 0.75  # 25% student discount
        print(f"Student discount applied. Ticket price: ${ticket_price}")
    else:
        print(f"Standard adult price: ${ticket_price}")

Output:

Student discount applied. Ticket price: $9.375

The second if statement is nested inside the first else block. It only runs for adults.

💡 DO: Use Nested Ifs When Choices are Hierarchical
Think "Is the person a child? If not, are they a student?" This step-by-step logic keeps your code clean and mirrors real-world decision-making.

🎯 5. Introducing Python Match (Python 3.10+): The Super-Powered Switch

The match statement (sometimes called structural pattern matching) is a newer, cleaner way to handle multiple, specific conditions. It's like a super-charged version of if...elif...elif for when you're checking the value of a single variable.

💡 Version Note:
match requires Python 3.10 or newer. Run python3 --version in your terminal first — if you're on an older version, stick with if...elif chains (they work identically, just more verbosely) until you can upgrade.

Why Use Match? The "Weekday Planner" Example

Imagine planning your day based on the weekday. With if...elif, it works but can be clunky.

📌 What this code does: checks day against each weekday name one at a time using repeated == comparisons, landing on the "Team meeting day" plan for Tuesday.
# The 'if...elif' way
day = "Tuesday"

if day == "Monday":
    plan = "Start new work projects."
elif day == "Tuesday":
    plan = "Team meeting day."
elif day == "Wednesday":
    plan = "Mid-week review."
elif day == "Thursday":
    plan = "Client calls."
elif day == "Friday":
    plan = "Wrap up work, plan weekend."
else:
    plan = "Relax! It's the weekend."

print(f"Today is {day}. Plan: {plan}")

This is repetitive—we keep writing day ==. The match statement simplifies this pattern beautifully.

The Match Statement: Clean and Readable

Here's the same logic with match:

📌 What this code does: the exact same logic as above, rewritten with match/case. Python compares day against each case value top to bottom and runs the first one that matches — case _ is the catch-all if nothing else fits.
day = "Tuesday"

match day:
    case "Monday":
        plan = "Start new work projects."
    case "Tuesday":
        plan = "Team meeting day."
    case "Wednesday":
        plan = "Mid-week review."
    case "Thursday":
        plan = "Client calls."
    case "Friday":
        plan = "Wrap up work, plan weekend."
    case _:  # The underscore `_` is the "catch-all" default case
        plan = "Relax! It's the weekend."

print(f"Today is {day}. Plan: {plan}")

Output:

Today is Tuesday. Plan: Team meeting day.

Isn't that cleaner? No more repeating the variable name. It's like telling Python: "Here's the value. Now, find which case it matches and run that code."

⚠️ IMPORTANT: The Default Case `_`
The underscore _ in a match statement acts exactly like the final else:. It catches any value that didn't match the previous cases. Always include it to handle unexpected inputs gracefully.

⚡ 6. Level Up: Advanced Match Features

Match is not just for simple values. It can unpack data and match patterns, which is incredibly powerful.

Matching with Lists (Pattern Unpacking)

Let's say you have a list representing a command for a robot: ["move", "north", 10] means "move north 10 units". We can use match to understand this command.

📌 What this code does: matches the shape of the command list, not just its value. Because it has exactly 3 items starting with "move", Python unpacks the 2nd and 3rd items directly into the new variables direction and distance for you to use immediately.
command = ["move", "east", 5]

match command:
    case ["move", direction, distance]:
        print(f"Robot moving {distance} units to the {direction}.")
    case ["rotate", degrees]:
        print(f"Robot rotating by {degrees} degrees.")
    case ["stop"]:
        print("Robot stopping.")
    case _:
        print("Error: Command not understood.")

Output:

Robot moving 5 units to the east.

Magic! The match statement didn't just check the value; it unpacked the list into variables (direction and distance) that we could use directly.

Matching with Conditions (Guard Clauses)

You can add an if condition inside a case to make it more specific. This is called a "guard".

Example: Processing user input with validation.

📌 What this code does: unpacks the user_input tuple into username/password, then checks each case's guard (the if part) in order — the first guard whose pattern and condition are both true wins.
user_input = ("admin", "password123")

match user_input:
    case (username, password) if username == "admin" and password == "secret":
        print("Access granted to super-admin panel.")
    case (username, password) if username == "admin":
        print("Access granted to standard admin panel.")
    case (_, _):
        print("Access denied. Invalid credentials.")

Output:

Access granted to standard admin panel.
🔬 Verified correction — and a real bug worth learning from: we ran this in a live interpreter and the actual output is "Access granted to standard admin panel.", not "Access denied." as sometimes claimed. Here's why it matters: the first guard fails because the password is "password123", not "secret" — but the second guard only checks the username, not the password at all. So any password paired with "admin" silently falls through to standard admin access. This is a textbook example of a security bug caused by an incomplete guard clause — always make sure every guard in a sensitive check re-validates every field, not just the ones that changed.

The guard (if username == "admin") adds an extra check after the pattern matches. It makes your matching logic incredibly precise — but as the example above shows, it's only as safe as the conditions you actually write.

🚫 DON'T: Overuse Advanced Match Early
Pattern matching is powerful, but for simple "value equals X" checks, a simple if...elif or a dictionary might be clearer. Use match when you truly need its pattern-unpacking superpowers.

🍕 7. Final Project: The Ultimate Food Order System

Let's combine everything into a mini-project. We'll build a system that takes an order and calculates the bill with discounts and combos.

📌 What this code does: uses match to look up the base menu price for an (item, size) pair, then uses plain if statements to layer on a takeaway fee and a loyalty discount, finishing with a formatted receipt — showing match and if working together, each doing what it's best at.
# The Ultimate Food Order System
item = "Pizza"
size = "Large"
is_takeaway = True
loyalty_member = False

base_price = 0

# Step 1: Set base price with MATCH (perfect for specific values)
match (item, size):
    case ("Burger", "Regular"):
        base_price = 5.99
    case ("Burger", "Large"):
        base_price = 8.99
    case ("Pizza", "Regular"):
        base_price = 10.99
    case ("Pizza", "Large"):
        base_price = 15.99
    case ("Salad", _):  # Any size salad is same price
        base_price = 7.49
    case _:
        print("Item/size not on menu.")
        base_price = 0

# Step 2: Apply modifiers with IF...ELIF
final_price = base_price

if is_takeaway:
    # Add a packaging fee
    final_price += 1.50
    print("Added takeaway packaging fee: $1.50")

if loyalty_member and base_price > 0:
    # Apply a 10% discount
    discount = base_price * 0.10
    final_price -= discount
    print(f"Loyalty discount applied: -${discount:.2f}")

# Step 3: Final output
if base_price > 0:
    print("=" * 30)
    print(f"ORDER SUMMARY:")
    print(f"Item: {size} {item}")
    print(f"Base Price: ${base_price:.2f}")
    print(f"Total Due: ${final_price:.2f}")
else:
    print("Cannot process order.")

Output:

Added takeaway packaging fee: $1.50
==============================
ORDER SUMMARY:
Item: Large Pizza
Base Price: $15.99
Total Due: $17.49

This project shows the perfect team: match elegantly handles the menu lookup (matching specific pairs), while if statements handle the conditional add-ons and discounts.

💡 Extend it further: try adding a loyalty_member = True test run yourself, or add a new menu item — since base_price > 0 gates the loyalty discount, you'll see firsthand why that guard exists (it stops a discount being calculated on an unrecognized $0 order).

⚖️ 8. Quick Comparison & When to Use What

  • Use if...elif...else when:
    • Conditions are based on ranges (score > 90).
    • You need to check multiple, unrelated variables.
    • Your logic is complex with many and/or operators.
  • Use match when:
    • You are checking the exact value of a single variable.
    • You have many specific cases (like days of the week, menu items).
    • You want to unpack data from lists or tuples as part of the check.
    • You're using Python 3.10 or newer.
💡 DO: Choose the Tool That Makes Your Code Most Readable
The best control flow tool is the one that makes your intention crystal clear to the next person reading your code (who might be you in 6 months!). Readability is a superpower.

📚 9. Quick Reference Cheat Sheet

📚 Control Flow Cheat Sheet

Basic if / elif / else:

if condition:
    pass                # runs if True
elif other_condition:
    pass                # checked only if first is False
else:
    pass                # runs if nothing above matched

Nesting:

if outer_condition:
    if inner_condition:
        pass            # runs only if BOTH are True

Match / case (Python 3.10+):

match value:
    case "a":
        pass
    case "b" | "c":     # OR pattern: matches "b" OR "c"
        pass
    case [x, y]:        # unpacks a 2-item list/tuple
        pass
    case (x, y) if x > 0:  # guard clause
        pass
    case _:
        pass            # catch-all, always include this

🚧 10. Common Pitfalls (And How to Avoid Them)

❌ Pitfall 1: Ordering conditions from broad to narrow
📌 What this code does: compares a broken ordering (broad check first) against the correct ordering (narrowest/highest threshold first).
# Bad — this ALWAYS matches first for any score >= 40, so
# an "A" score of 95 gets mislabeled as a "D"!
if score >= 40:
    print("D")
elif score >= 90:
    print("A")  # unreachable!

# Good — most specific / highest threshold checked first
if score >= 90:
    print("A")
elif score >= 40:
    print("D")
❌ Pitfall 2: Forgetting the catch-all case `_` in match
# Risky — if day is something unexpected (e.g. a typo),
# nothing runs and 'plan' is never defined -> NameError later
match day:
    case "Monday":
        plan = "Work"
    case "Tuesday":
        plan = "Meetings"

# Safe — always handle the unexpected
match day:
    case "Monday":
        plan = "Work"
    case "Tuesday":
        plan = "Meetings"
    case _:
        plan = "Unknown day"
❌ Pitfall 3: A guard clause that doesn't check everything it implies
# Bad — this guard implies "verified admin" but only checks username
case (username, password) if username == "admin":
    print("Access granted")   # password is never actually checked here!

# Good — every guard on a sensitive path re-validates every field
case (username, password) if username == "admin" and password == correct_password:
    print("Access granted")
❌ Pitfall 4: Using match on Python older than 3.10
# This raises a SyntaxError on Python 3.9 or earlier
match day:
    case "Monday":
        pass

# Check your version first:
# python3 --version

🏋️ 11. Practice Exercises

✅ Exercise 1: Traffic Light Simulator
Write an if...elif...else chain that takes a traffic light color ("red", "yellow", "green") and prints "Stop", "Slow down", or "Go".
✅ Exercise 2: Rewrite With Match
Rewrite Exercise 1 using match...case instead. Add a case _: that handles an invalid color.
✅ Exercise 3: Nested Discount Logic
Extend the movie ticket example: add a senior discount (age ≥ 65 gets 40% off, checked before the student discount) using nested if statements.
✅ Exercise 4: Command Parser with Guards
Using match and pattern unpacking, write a parser for commands like ["deposit", 100] and ["withdraw", 50]. Add a guard clause that rejects withdrawals greater than a fixed balance.
✅ Exercise 5: Fix the Bug
Take the guard-clause login example from this post and fix it so a real password check is enforced on every case that grants access — not just the super-admin case.

❓ 12. Frequently Asked Questions

What is control flow in Python?
Control flow is the order in which a program's statements execute. Tools like if, elif, else, and match let a program branch — running different code depending on a condition — instead of always executing top to bottom.

What Python version do I need for the match statement?
match...case requires Python 3.10 or later. Check your version with python3 --version; on earlier versions, use an if...elif...else chain instead — the logic is equivalent.

Is Python's match statement the same as a switch statement in other languages?
It's similar in spirit but more powerful. Unlike a traditional switch, Python's match can unpack data structures (like lists and tuples) directly into variables and supports guard clauses (extra if conditions) on each case.

When should I use if...elif instead of match?
Use if...elif when your conditions are ranges (score > 90), involve multiple unrelated variables, or rely on complex and/or logic. Use match when you're checking one variable against several specific, exact values — or when you want to unpack a structure like a list or tuple as part of the check.

What does the underscore _ mean in a match statement?
It's the wildcard/catch-all pattern — equivalent to a final else:. It matches anything that didn't match an earlier case, and you should always include one so unexpected input doesn't silently fall through with no branch executed.

What is a guard clause in Python pattern matching?
A guard is an if condition attached to a case, written as case pattern if condition:. The case only matches if both the structural pattern and the guard condition are true — but as shown in this post's login example, a guard that doesn't check every relevant value can silently create a bug.

📝 13. Summary: You Are Now a Control Flow Master! 🏆

You've learned how to make your code think:

  • if: Makes a simple yes/no decision.
  • if...else: Chooses between two clear paths.
  • if...elif...else: Navigates a multi-branch decision tree.
  • match...case: Elegantly matches a value against many specific options and can unpack data.
  • Guard clauses: add extra precision to match — but must check every relevant condition, not just the first one that changed.

Ready for the next step? Continue to Python Functions, where you'll package this decision-making logic into clean, reusable blocks of code.

Happy coding! ✨

Comments