22 min read

Up to now, every program you've written runs straight through from top to bottom. Line 1, line 2, line 3, done. That's fine for greeting the user and doing arithmetic, but it's not how real software works. Real software makes decisions. Should we...

Learning Objectives

  • Write if, if-else, and if-elif-else statements to control program flow
  • Combine conditions using the Boolean operators and, or, and not
  • Explain short-circuit evaluation and identify when it matters
  • Choose between nested conditionals, elif chains, and match/case for readability
  • Debug common conditional errors including = vs == and overlapping conditions
  • Apply input validation to reject bad data before processing it

Chapter 4: Making Decisions: Conditionals and Boolean Logic

"All programming is an exercise in applied logic." — Robert C. Martin

Chapter Overview

Up to now, every program you've written runs straight through from top to bottom. Line 1, line 2, line 3, done. That's fine for greeting the user and doing arithmetic, but it's not how real software works. Real software makes decisions. Should we approve the loan or deny it? Is the password correct? Is the player alive or dead? Is the temperature above freezing?

Every interesting program you've ever used — every game, every website, every app on your phone — makes thousands of decisions per second. And every single one of those decisions boils down to the same fundamental mechanism: evaluate a condition, then choose a path.

That mechanism is the conditional statement, and it's what this chapter is about. By the end, you'll be able to write programs that respond differently to different inputs, validate data before processing it, and build decision trees that handle complex real-world logic. You'll also meet Python's match/case statement, a powerful tool for routing decisions that arrived in Python 3.10.

More importantly, you'll understand the threshold concept behind all of this: computers don't "decide" anything. They evaluate conditions that resolve to True or False, and they follow whichever path you've mapped to each outcome. Boolean logic is the language of decision-making, and once you're fluent in it, you can teach a computer to make any decision you can express precisely.

In this chapter, you will learn to: - Write if, if-else, and if-elif-else statements to control program flow - Combine conditions using the Boolean operators and, or, and not - Explain short-circuit evaluation and identify when it matters - Choose between nested conditionals, elif chains, and match/case for readability - Debug common conditional errors including = vs == and overlapping conditions - Apply input validation to reject bad data before processing it

🏃 Fast Track: If you're comfortable with if/else from another language, skim 4.2-4.3, read 4.5-4.6 carefully (Python's Boolean operators have quirks), and study 4.8 (match/case) — it's likely new.

🔬 Deep Dive: After this chapter, read Case Study 1 (Building a Simple Chatbot with Conditionals) and work through the exercises in Parts D and E for multi-branch logic challenges.


🔄 Spaced Review: Before We Start

Before diving into new material, let's quickly revisit concepts from earlier chapters. Try to answer from memory before checking.

From Chapter 2 (Your First Program): What function do you use to get text input from the user, and what type does it always return?

Verify The `input()` function reads text from the user. It **always returns a string**, even if the user types a number. You must convert it explicitly with `int()`, `float()`, etc.

From Chapter 3 (Variables, Types, Expressions): What is the difference between = and == in Python?

Verify `=` is the **assignment operator** — it gives a value to a variable (`x = 5`). `==` is the **equality comparison operator** — it checks whether two values are equal and returns `True` or `False` (`x == 5`). Confusing them is one of the most common beginner bugs, and we'll revisit it in detail in this chapter.

Bridge to this chapter: If input() always returns a string, what happens when a user types "25" and you try to check if it's greater than 18? Will "25" > 18 work?

Verify No — comparing a string to an integer with `>` raises a `TypeError`. You must convert the input first: `int("25") > 18` evaluates to `True`. This is exactly the kind of input validation we'll build in this chapter.

4.1 Programs That Make Decisions

Think about the decisions you make before leaving the house in the morning. Is it raining? Grab an umbrella. Is it below 40°F? Wear a coat. Is it a weekday? Head to campus. Is it Saturday? Sleep in.

You make dozens of decisions like these without thinking about them. But notice something important: each decision has the same structure. There's a condition (is it raining?) and there's an action that depends on whether that condition is true or false (grab umbrella / don't grab umbrella).

Programs work exactly the same way. A conditional is a programming construct that executes different code depending on whether a condition is true or false. That's it. No magic, no artificial intelligence — just "if this is true, do this; otherwise, do that."

Here's why this matters so much: without conditionals, programs can only do one thing. They're like a train on a single track — they go forward, and that's it. Conditionals are the switches that let the train take different tracks depending on the situation. They're what transform a calculator into a program.

💡 Intuition: Think of a conditional as a fork in the road. Your program is walking along, executing statements in order, and suddenly it reaches a fork. A sign at the fork asks a yes/no question. Based on the answer, the program takes one path or the other. After the fork, the paths may reconverge, and the program continues forward.

Let's look at where conditionals show up in real software:

Application Decision Condition
Login system Grant or deny access Is the password correct?
Online store Apply discount Is the cart total over $50?
Weather app Display icon Is it above 32°F (rain vs. snow)?
Video game Player takes damage Did the enemy's attack hit?
ATM Dispense cash Is the balance sufficient?
Grading system Assign letter grade What score range does this fall into?

Every one of these is a conditional. And every one follows the same Boolean logic you're about to learn.


4.2 The if Statement

The simplest conditional in Python is the if statement. It evaluates a condition, and if that condition is True, it executes a block of code. If the condition is False, it skips that block entirely.

Syntax

if condition:
    # This code runs only when condition is True
    statement_1
    statement_2

Three things to notice:

  1. The condition is any expression that evaluates to True or False (a Boolean expression).
  2. The colon (:) at the end of the if line is required. Forgetting it is a SyntaxError.
  3. The indented block defines what happens when the condition is true. Python uses indentation — typically 4 spaces — to define blocks. This is not optional decoration; it's how Python knows which statements belong to the if.

⚠️ Pitfall: In many languages, blocks are defined by curly braces {}. In Python, indentation is the syntax. Mixing tabs and spaces, or using inconsistent indentation, will cause IndentationError. Configure your editor to insert 4 spaces when you press Tab.

Your First if

temperature = 28

if temperature < 32:
    print("It's below freezing!")
    print("Watch out for ice on the roads.")

print("Have a good day!")

Expected output:

It's below freezing!
Watch out for ice on the roads.
Have a good day!

The condition temperature < 32 evaluates to True (because 28 is less than 32), so both indented print statements execute. The final print runs regardless — it's not indented under the if, so it's not part of the conditional block.

Now change temperature to 45:

temperature = 45

if temperature < 32:
    print("It's below freezing!")
    print("Watch out for ice on the roads.")

print("Have a good day!")

Expected output:

Have a good day!

This time temperature < 32 is False, so the indented block is skipped entirely. The program jumps straight to the un-indented print.

📊 Flowchart Description: Imagine a diamond shape labeled "temperature < 32?" with two arrows coming out of it. The arrow labeled "True" leads to a box that says "Print freezing warnings," which then flows down. The arrow labeled "False" goes directly down, bypassing that box. Both paths converge at a box that says "Print 'Have a good day!'"

Elena Validates Input

Elena from our running example has been building a data processing script. She needs to check whether a donation amount is negative — a clear data entry error — before including it in her totals.

donation = float(input("Enter donation amount: $"))

if donation < 0:
    print(f"Warning: ${donation} is negative. Skipping this entry.")

print("Processing complete.")

If user enters 50:

Enter donation amount: $50
Processing complete.

If user enters -25:

Enter donation amount: $-25
Warning: $-25.0 is negative. Skipping this entry.
Processing complete.

This is a simple validation check — one of the most common uses of if in real code. We're not fixing the bad data yet (we'll learn how in Chapter 5 with loops), but we're detecting it.


4.3 if-else: Two Paths

Often you don't just want to do something when a condition is true — you want to do one thing if it's true and a different thing if it's false. That's what else is for.

Syntax

if condition:
    # Runs when condition is True
    do_this()
else:
    # Runs when condition is False
    do_that()

The else clause catches everything that the if doesn't. There's no condition on the else — it's the "otherwise" path.

Voting Age Example

age = int(input("How old are you? "))

if age >= 18:
    print("You are eligible to vote.")
    print("Make sure you're registered!")
else:
    years_left = 18 - age
    print(f"You can't vote yet. {years_left} year(s) to go.")

print("Thanks for checking!")

If user enters 21:

How old are you? 21
You are eligible to vote.
Make sure you're registered!
Thanks for checking!

If user enters 15:

How old are you? 15
You can't vote yet. 3 year(s) to go.
Thanks for checking!

Notice that exactly one of the two blocks runs — never both, never neither. The if and else are mutually exclusive paths.

📊 Flowchart Description: A diamond labeled "age >= 18?" with two outgoing arrows. The "True" arrow leads to "Print voting eligibility." The "False" arrow leads to "Calculate years left, print wait message." Both arrows then converge at "Print thanks."

Pass/Fail Grade Check

score = float(input("Enter your score (0-100): "))

if score >= 60:
    print(f"Score: {score} — PASS")
else:
    print(f"Score: {score} — FAIL")

If user enters 73.5:

Enter your score (0-100): 73.5
Score: 73.5 — PASS

If user enters 42:

Enter your score (0-100): 42
Score: 42.0 — FAIL

Simple, clean, effective. But what if you need more than two categories?


🧩 Productive Struggle: The Multi-Grade Problem

Before reading the next section, try this challenge. You have a numeric score from 0 to 100, and you need to print the corresponding letter grade:

Score Range Grade
90–100 A
80–89 B
70–79 C
60–69 D
Below 60 F

Using only if and else (no elif — you haven't learned it yet), how would you solve this? Take 5 minutes to write it out before reading on.

One possible approach (try first!)
score = int(input("Enter score: "))

if score >= 90:
    print("A")
else:
    if score >= 80:
        print("B")
    else:
        if score >= 70:
            print("C")
        else:
            if score >= 60:
                print("D")
            else:
                print("F")
This works! But notice how the indentation keeps drifting to the right with each nested `if-else`. By the time you get to "F," you're deeply nested. If you had 10 grade categories, this would be unreadable. There must be a better way — and there is.

4.4 if-elif-else: Multiple Paths

The elif keyword (short for "else if") lets you chain multiple conditions without nesting. It's Python's solution to the "staircase of indentation" problem you just saw.

Syntax

if condition_1:
    block_1
elif condition_2:
    block_2
elif condition_3:
    block_3
else:
    default_block

Python evaluates the conditions top to bottom and executes the block for the first condition that is True. Once a match is found, all remaining elif and else blocks are skipped. If no condition is True, the else block runs (if present).

The Grade Calculator

Here's the clean version of the grade calculator, using our running example:

score = int(input("Enter your score (0-100): "))

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
elif score >= 60:
    grade = "D"
else:
    grade = "F"

print(f"Score: {score} → Grade: {grade}")

If user enters 85:

Enter your score (0-100): 85
Score: 85 → Grade: B

If user enters 72:

Enter your score (0-100): 72
Score: 72 → Grade: C

If user enters 55:

Enter your score (0-100): 55
Score: 55 → Grade: F

Let's trace through the 85 case step by step: 1. score >= 9085 >= 90False — skip this block 2. score >= 8085 >= 80True — execute this block, set grade = "B" 3. All remaining elif/else blocks are skipped — we already found a match 4. Continue to print

💡 Intuition: Think of an if-elif-else chain like a security checkpoint with multiple gates. You walk up to the first gate — if it lets you through, you're done; you don't visit the other gates. If the first gate rejects you, you try the second. The else at the end is the "everyone else" gate that lets through anyone who wasn't caught by a previous gate.

Order Matters

The order of conditions in an elif chain is critical. Watch what happens if we reverse the grade calculator:

# BUG: Wrong order!
score = int(input("Enter your score (0-100): "))

if score >= 60:
    grade = "D"  # A score of 95 would match here!
elif score >= 70:
    grade = "C"  # This never runs for scores >= 70
elif score >= 80:
    grade = "B"  # This never runs
elif score >= 90:
    grade = "A"  # This never runs
else:
    grade = "F"

print(f"Score: {score} → Grade: {grade}")

A student with a score of 95 would get a "D" because 95 >= 60 is True, and that's the first condition checked. Every score of 60 or above hits the first branch. The conditions are evaluated top-to-bottom, so put the most restrictive condition first.

⚠️ Pitfall: When your elif conditions involve ranges, work from most restrictive to least restrictive (highest threshold first), or from most specific to most general. If your tests aren't mutually exclusive by design, put the tightest filter at the top.

🔄 Check Your Understanding

Without running the code, what does this print? python x = 15 if x > 20: print("high") elif x > 10: print("medium") elif x > 5: print("low") else: print("tiny")

Verify

It prints medium. The first condition (15 > 20) is False. The second condition (15 > 10) is True, so "medium" prints and the rest is skipped. Even though 15 > 5 is also True, that branch is never reached.


🚪 Threshold Concept: Boolean Logic as the Language of Decision-Making

Here's the insight that changes how you think about programming:

Computers don't "decide" anything. They don't weigh pros and cons. They don't have intuition or judgment. Every "decision" a computer makes is a Boolean expression that evaluates to exactly one of two values: True or False.

When Netflix "decides" what to recommend, it's evaluating thousands of Boolean conditions: "Is this user's similarity score to other users who liked this movie above 0.7?" — True or False. When a self-driving car "decides" to brake, it's evaluating: "Is there an object within 3 meters in the forward path?" — True or False.

Before this clicks: "The program decides what to do based on the input." After this clicks: "The program evaluates a Boolean expression. If it's True, it follows path A. If it's False, it follows path B. There is no decision — there is only evaluation."

This matters because it means you can always reason about what a conditional will do. There's no ambiguity, no subjectivity, no "it depends." Given specific values, a Boolean expression always produces the same result. This is what makes programs predictable — and debuggable.

The bool type in Python has exactly two values: True and False. Every comparison operator (<, >, <=, >=, ==, !=) returns a bool. Every if statement evaluates an expression and checks whether it's True or False. That's the entire mechanism.

print(type(True))         # <class 'bool'>
print(type(5 > 3))        # <class 'bool'>
print(5 > 3)              # True
print(10 == 20)           # False
print(type(10 == 20))     # <class 'bool'>

Expected output:

<class 'bool'>
<class 'bool'>
True
False
<class 'bool'>

Everything in conditionals flows from this simple foundation.


4.5 Boolean Operators: and, or, not

Simple conditions check one thing. But real decisions often involve multiple factors. "Is the user logged in and is their account active?" "Is the temperature below freezing or is it raining?" "The store is open if it is not a holiday."

Python provides three Boolean operators for combining conditions:

Operator Meaning Returns True when...
and Both must be true Both operands are True
or At least one must be true At least one operand is True
not Reversal The operand is False

and: Both Must Be True

age = 25
has_license = True

if age >= 16 and has_license:
    print("You can drive.")
else:
    print("You cannot drive.")

Expected output:

You can drive.

Both conditions must be True for the combined expression to be True. If the age were 14, the first condition would be False, and the whole and expression would be False — regardless of has_license.

or: At Least One Must Be True

day = "Saturday"

if day == "Saturday" or day == "Sunday":
    print("It's the weekend!")
else:
    print("It's a weekday.")

Expected output:

It's the weekend!

With or, the expression is True if either operand (or both) is True.

⚠️ Pitfall: A very common beginner mistake is writing if day == "Saturday" or "Sunday":. This does NOT check whether day is "Sunday." It checks whether day == "Saturday" is True OR whether the string "Sunday" is truthy (and non-empty strings are always truthy in Python). The correct form is if day == "Saturday" or day == "Sunday":.

not: Reversal

is_raining = False

if not is_raining:
    print("No umbrella needed.")

Expected output:

No umbrella needed.

not flips True to False and False to True.

Truth Tables

A truth table shows every possible combination of inputs and the resulting output. Memorize these — they're the foundation of Boolean logic.

and Truth Table:

A B A and B
True True True
True False False
False True False
False False False

or Truth Table:

A B A or B
True True True
True False True
False True True
False False False

not Truth Table:

A not A
True False
False True

Operator Precedence

When you combine and, or, and not in the same expression, Python evaluates them in this order:

  1. not (highest priority — evaluated first)
  2. and
  3. or (lowest priority — evaluated last)

This means a or b and c is evaluated as a or (b and c), not (a or b) and c. When in doubt, use parentheses to make your intent explicit — your future self (and your teammates) will thank you.

# Without parentheses — relies on precedence
x = True or False and False
print(x)  # True (because: True or (False and False) → True or False → True)

# With explicit parentheses — same result, but clearer
x = True or (False and False)
print(x)  # True

Expected output:

True
True

✅ Best Practice: Always use parentheses when mixing and and or. Even if you know the precedence rules, parentheses make the code's intent obvious to anyone reading it — including you in six months.

A Realistic Example: Ride Eligibility

height_cm = int(input("Height in cm: "))
age = int(input("Age: "))
has_parent = input("Parent present? (yes/no): ").lower() == "yes"

if height_cm >= 120 and (age >= 12 or has_parent):
    print("You may ride the rollercoaster!")
else:
    print("Sorry, you cannot ride.")

This encodes the rule: "You must be at least 120 cm tall, AND you must either be 12+ years old or have a parent present." The parentheses around age >= 12 or has_parent are essential — without them, and would bind to age >= 12 first, changing the logic entirely.


4.6 Short-Circuit Evaluation

Here's something subtle and important: Python doesn't always evaluate both sides of an and or or expression.

Short-circuit evaluation means Python stops evaluating as soon as it knows the final result:

  • and: If the left side is False, the whole expression is False — Python doesn't even look at the right side.
  • or: If the left side is True, the whole expression is True — Python skips the right side.

Why does this matter? Two reasons.

Reason 1: Performance

If the right side involves an expensive computation (like querying a database), short-circuiting avoids unnecessary work.

# Python won't call expensive_check() if is_cached is True
if is_cached or expensive_check():
    process_data()

Reason 2: Safety Guards

Short-circuiting lets you write safety checks that prevent errors:

user_input = ""

# Safe: if user_input is empty, Python never evaluates user_input[0]
if len(user_input) > 0 and user_input[0] == "#":
    print("This is a comment.")
else:
    print("Not a comment.")

Expected output:

Not a comment.

If we didn't have short-circuit evaluation, user_input[0] would crash with an IndexError because you can't access the first character of an empty string. But because len(user_input) > 0 is False, Python short-circuits the and and never evaluates user_input[0].

This pattern — "check if it's safe, then do the thing" — shows up constantly in real code.

# Safely check a division
denominator = 0
if denominator != 0 and (100 / denominator) > 10:
    print("Big ratio!")
else:
    print("Denominator is zero or ratio is small.")

Expected output:

Denominator is zero or ratio is small.

Without short-circuiting, 100 / 0 would raise a ZeroDivisionError. But Python sees that denominator != 0 is False and skips the division entirely.

💡 Intuition: Short-circuit evaluation is like a bouncer at a club with two requirements (e.g., ID and dress code). If you fail the ID check, the bouncer doesn't even look at your outfit — you're already rejected. If the first and condition is False, there's no point checking the second.

🔄 Check Your Understanding

Will the following code crash? Why or why not? python values = [] if len(values) > 0 and values[0] > 10: print("First value is large")

Verify

No, it won't crash. len(values) > 0 evaluates to False (the list is empty), so Python short-circuits the and and never evaluates values[0] > 10. Without short-circuiting, values[0] would raise an IndexError.


4.7 Nested Conditionals

Sometimes you need a conditional inside another conditional. This is called a nested conditional.

age = int(input("Age: "))
has_id = input("Do you have ID? (yes/no): ").lower() == "yes"

if age >= 21:
    if has_id:
        print("You may enter the venue.")
    else:
        print("You're old enough, but you need ID.")
else:
    print("Sorry, you must be 21 or older.")

If user enters 25 and yes:

Age: 25
Do you have ID? (yes/no): yes
You may enter the venue.

If user enters 25 and no:

Age: 25
Do you have ID? (yes/no): no
You're old enough, but you need ID.

If user enters 18:

Age: 18
Do you have ID? (yes/no): yes
Sorry, you must be 21 or older.

When to Nest vs. When to Flatten

Nested conditionals are sometimes necessary — as in the example above, where the inner decision (has_id) only makes sense in the context of the outer decision (age >= 21). But deep nesting quickly becomes hard to read.

Rule of thumb: If your code is indented more than 3 levels deep, look for ways to flatten it.

Here's a deeply nested version versus a flattened version of the same logic:

# Deeply nested — hard to follow
if is_student:
    if gpa >= 3.0:
        if not has_violations:
            print("Eligible for scholarship")
        else:
            print("Not eligible: violations on record")
    else:
        print("Not eligible: GPA too low")
else:
    print("Not eligible: not a student")
# Flattened with and — easier to read
if is_student and gpa >= 3.0 and not has_violations:
    print("Eligible for scholarship")
elif not is_student:
    print("Not eligible: not a student")
elif gpa < 3.0:
    print("Not eligible: GPA too low")
else:
    print("Not eligible: violations on record")

Both versions produce the same output for all inputs. The flattened version is easier to scan, easier to modify, and less prone to the "dangling else" problem (where it's unclear which if an else belongs to).

✅ Best Practice: Prefer flat if-elif-else chains over deeply nested conditionals when possible. Nesting is appropriate when the inner decision logically depends on the outer one, but if you can express the same logic with and/or, the flat version is usually clearer.


4.8 The match/case Statement

Python 3.10 introduced structural pattern matching with match/case. It's useful when you're comparing one value against several specific options — like a menu selection, a command name, or a status code.

Syntax

match variable:
    case value_1:
        block_1
    case value_2:
        block_2
    case _:
        default_block

The underscore _ in the last case is the wildcard pattern — it matches anything that wasn't caught by a previous case. It's like the else in an if-elif-else chain.

print("=== Crypts of Pythonia ===")
print("You stand at a crossroads.")
print("1. Take the dark corridor")
print("2. Open the wooden door")
print("3. Climb the spiral staircase")
print("4. Check your inventory")

choice = input("Your choice (1-4): ")

match choice:
    case "1":
        print("You creep down the dark corridor...")
        print("A cold draft chills your bones.")
    case "2":
        print("The wooden door creaks open...")
        print("A dimly lit room reveals a treasure chest!")
    case "3":
        print("You climb the spiral staircase...")
        print("At the top, you find a locked door with a riddle.")
    case "4":
        print("Inventory: torch, rusty key, stale bread")
    case _:
        print(f"'{choice}' is not a valid option. Try 1-4.")

If user enters 2:

=== Crypts of Pythonia ===
You stand at a crossroads.
1. Take the dark corridor
2. Open the wooden door
3. Climb the spiral staircase
4. Check your inventory
Your choice (1-4): 2
The wooden door creaks open...
A dimly lit room reveals a treasure chest!

If user enters 7:

Your choice (1-4): 7
'7' is not a valid option. Try 1-4.

When to Use match/case vs. if-elif-else

Use if-elif-else when... Use match/case when...
Conditions involve ranges or inequalities (score >= 90) You're comparing one value against discrete options
Conditions use and, or, not Each case is a specific value or pattern
You need complex Boolean expressions You want cleaner syntax for "dispatch" logic
Supporting Python < 3.10 You know your environment has Python 3.10+

The grade calculator is a poor fit for match/case because the conditions are ranges (>= 90, >= 80, etc.). The text adventure menu is a perfect fit because each case is a specific string value.

💡 Intuition: Think of match/case as a switchboard operator routing a call. You give them a number, and they connect you to the right line. Think of if-elif-else as a series of questions a doctor asks: "Is the temperature above 103? Do you have a cough? Are you nauseous?" Each question can be arbitrarily complex.

Pattern Matching with Multiple Values

You can match multiple values in a single case using the | (pipe) operator:

day = input("Enter a day: ").lower()

match day:
    case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
        print("It's a weekday.")
    case "saturday" | "sunday":
        print("It's the weekend!")
    case _:
        print(f"'{day}' is not a recognized day.")

If user enters Saturday:

Enter a day: Saturday
It's the weekend!

This is cleaner than writing if day == "monday" or day == "tuesday" or day == "wednesday" ....


4.9 Common Patterns and Pitfalls

After grading thousands of CS1 assignments, here are the mistakes I see most often — and how to avoid them.

🐛 Debugging Walkthrough: = vs. ==

This is the single most common conditional bug for beginners.

# BUG: This is assignment, not comparison!
password = "secret123"
user_input = input("Enter password: ")

if user_input = "secret123":  # SyntaxError!
    print("Access granted")

What happens: Python raises a SyntaxError because = is assignment, not comparison. You're trying to assign "secret123" to user_input inside the if condition, which Python doesn't allow.

The fix: Use == for comparison.

password = "secret123"
user_input = input("Enter password: ")

if user_input == "secret123":
    print("Access granted")
else:
    print("Access denied")

If user enters secret123:

Enter password: secret123
Access granted

Python actually protects you here — unlike C, where if (x = 5) silently assigns 5 to x and always evaluates as true. Python treats = inside an if as a SyntaxError, which is a deliberate safety feature. (Python 3.8 introduced the "walrus operator" := for assignment expressions, but that's an advanced topic for another day.)

🐛 Mnemonic: Single = means "becomes" (assignment: x = 5 means "x becomes 5"). Double == means "equals?" (comparison: x == 5 means "does x equal 5?").

Pitfall: Overlapping Conditions

# BUG: Conditions overlap — "excellent" never prints
score = 95

if score >= 70:
    print("Good")
elif score >= 90:
    print("Excellent")  # Dead code — never reached!

A score of 95 matches score >= 70 first, so "Good" prints. The elif score >= 90 is dead code — it can never execute because any score >= 90 is also >= 70 and gets caught by the first condition. Always order from most restrictive to least restrictive.

Pitfall: Forgetting That else Has No Condition

# BUG: else does not take a condition
if age >= 18:
    print("Adult")
else age < 18:  # SyntaxError!
    print("Minor")

else means "everything else" — it doesn't take a condition. If you want a condition, use elif.

Pitfall: Comparing Different Types

user_age = input("Enter your age: ")  # Returns a string!

if user_age >= 18:  # TypeError: '>=' not supported between str and int
    print("You're an adult")

Remember: input() always returns a string. You must convert it: user_age = int(input("Enter your age: ")).

Pattern: Clamping a Value

A common pattern is clamping (restricting) a value to a valid range:

score = int(input("Enter score: "))

if score > 100:
    score = 100
elif score < 0:
    score = 0

print(f"Adjusted score: {score}")

If user enters 150:

Enter score: 150
Adjusted score: 100

If user enters -20:

Enter score: -20
Adjusted score: 0

Pattern: Setting a Default

color = input("Favorite color (or press Enter for default): ")

if color == "":
    color = "blue"

print(f"Using color: {color}")

If user just presses Enter:

Favorite color (or press Enter for default):
Using color: blue

🔄 Check Your Understanding

What's wrong with this code? python temp = input("Temperature: ") if temp > 100: print("Boiling!")

Verify

input() returns a string, so temp is a string like "105". Comparing a string to an integer with > raises a TypeError. The fix: temp = int(input("Temperature: ")) or temp = float(input("Temperature: ")).


4.10 Project Checkpoint: TaskFlow v0.3

Time to upgrade TaskFlow. In v0.2 (Chapter 3), you could create a task with a name and timestamp. Now we'll add priority levels and input validation using the conditional logic from this chapter.

What We're Building

TaskFlow v0.3 adds: 1. Task priority: each task gets a priority of high, medium, or low 2. Input validation: reject empty task names and invalid priorities 3. Priority-based display: format the output differently based on priority

The Code

"""
TaskFlow v0.3 — Task priority and input validation.
Building on: v0.2 (Chapter 3) — task creation with timestamp.
New in v0.3: priority levels, input validation, conditional formatting.
"""

from datetime import datetime

# --- Get task name with validation ---
task_name = input("Enter task name: ").strip()

if task_name == "":
    print("Error: Task name cannot be empty.")
    print("Please restart and enter a valid task name.")
else:
    # --- Get priority with validation ---
    print("Priority levels: high, medium, low")
    priority = input("Enter priority: ").strip().lower()

    if priority != "high" and priority != "medium" and priority != "low":
        print(f"Error: '{priority}' is not a valid priority.")
        print("Please use 'high', 'medium', or 'low'.")
    else:
        # --- Create the task ---
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")

        # --- Format output based on priority ---
        if priority == "high":
            marker = "!!!"
            label = "HIGH PRIORITY"
        elif priority == "medium":
            marker = " ! "
            label = "MEDIUM"
        else:
            marker = " . "
            label = "low"

        # --- Display the task ---
        print()
        print("=" * 40)
        print(f"  [{marker}] {task_name}")
        print(f"  Priority: {label}")
        print(f"  Created:  {timestamp}")
        print("=" * 40)
        print()
        print("Task added to TaskFlow!")

Sample Runs

Run 1 — Valid high-priority task:

Enter task name: Submit midterm essay
Priority levels: high, medium, low
Enter priority: high

========================================
  [!!!] Submit midterm essay
  Priority: HIGH PRIORITY
  Created:  2025-09-15 14:30
========================================

Task added to TaskFlow!

Run 2 — Empty task name:

Enter task name:
Error: Task name cannot be empty.
Please restart and enter a valid task name.

Run 3 — Invalid priority:

Enter task name: Buy groceries
Priority levels: high, medium, low
Enter priority: urgent
Error: 'urgent' is not a valid priority.
Please use 'high', 'medium', or 'low'.

Run 4 — Valid low-priority task:

Enter task name: Organize desk
Priority levels: high, medium, low
Enter priority: LOW

========================================
  [ . ] Organize desk
  Priority: low
  Created:  2025-09-15 14:32
========================================

Task added to TaskFlow!

What's New in This Version

Let's trace the conditional logic:

  1. Empty name check (if task_name == ""): Uses a simple if-else to validate the task name. The .strip() call removes leading/trailing whitespace, so " " is treated as empty.

  2. Priority validation (if priority != "high" and priority != "medium" and priority != "low"): Uses and with not-equal checks. All three must be true (the priority doesn't match any valid option) for the error to trigger. The .lower() call makes input case-insensitive — "HIGH," "High," and "high" all work.

  3. Priority-based formatting (if/elif/else): A three-way branch that sets the marker and label based on priority. This is a classic elif use case.

Notice the nesting structure: the priority input only happens if the name is valid, and the task creation only happens if the priority is also valid. Each validation gate must pass before the program proceeds to the next step.

Looking Ahead

In Chapter 5, we'll wrap the validation in a loop so the program re-prompts instead of quitting on bad input. In Chapter 6, we'll refactor this into clean functions. But the conditional logic you've built here is the foundation for everything that follows.

Try It Yourself

Extend TaskFlow v0.3 with one or more of these enhancements: - Add a fourth priority level: "critical" (displayed with [***]) - Use match/case instead of if-elif-else for the priority formatting - Add validation that the task name must be at least 3 characters long - Add a due date field and validate that it's not in the past (hint: compare with datetime.now())


Chapter Summary

You've covered a lot of ground in this chapter. Here's the big picture:

The Core Mechanism: Every conditional evaluates a Boolean expression — something that resolves to True or False — and chooses a path based on the result.

The Three Structures: - if: Do something only when a condition is true - if-else: Choose between two paths - if-elif-else: Choose among multiple paths (first match wins)

Combining Conditions: Use and (both must be true), or (at least one), and not (reversal). Python evaluates not first, then and, then or — but use parentheses to be explicit.

Short-Circuit Evaluation: Python stops evaluating and at the first False and or at the first True. This enables safe guard patterns like if len(x) > 0 and x[0] == "#".

Pattern Matching: The match/case statement (Python 3.10+) is ideal for dispatching on specific values — menus, commands, status codes. Use if-elif-else for ranges and complex conditions.

The Threshold Concept: Computers don't decide. They evaluate. Boolean logic is the language you use to encode human decision-making into precise, unambiguous machine instructions.

In Chapter 5, we'll learn about loops — the construct that lets programs repeat actions. Combined with conditionals, loops unlock the full power of programming. Almost everything interesting you'll build in your career uses both.


What's Next: Chapter 5: Loops and Iteration — making the computer do the boring work for you. You'll finally be able to re-prompt the user when they give bad input, process lists of data, and build TaskFlow v0.4 with a menu-driven interface.