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
In This Chapter
- Chapter Overview
- 🔄 Spaced Review: Before We Start
- 4.1 Programs That Make Decisions
- 4.2 The if Statement
- 4.3 if-else: Two Paths
- 🧩 Productive Struggle: The Multi-Grade Problem
- 4.4 if-elif-else: Multiple Paths
- 🚪 Threshold Concept: Boolean Logic as the Language of Decision-Making
- 4.5 Boolean Operators: and, or, not
- 4.6 Short-Circuit Evaluation
- 4.7 Nested Conditionals
- 4.8 The match/case Statement
- 4.9 Common Patterns and Pitfalls
- 4.10 Project Checkpoint: TaskFlow v0.3
- Chapter Summary
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/elsefrom 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:
- The condition is any expression that evaluates to
TrueorFalse(a Boolean expression). - The colon (
:) at the end of theifline is required. Forgetting it is aSyntaxError. - 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 causeIndentationError. 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 >= 90 → 85 >= 90 → False — skip this block
2. score >= 80 → 85 >= 80 → True — 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-elsechain 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. Theelseat 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
elifconditions 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) isFalse. The second condition (15 > 10) isTrue, so"medium"prints and the rest is skipped. Even though15 > 5is alsoTrue, 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 whetherdayis "Sunday." It checks whetherday == "Saturday"isTrueOR whether the string"Sunday"is truthy (and non-empty strings are always truthy in Python). The correct form isif 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:
not(highest priority — evaluated first)andor(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
andandor. 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 isFalse, the whole expression isFalse— Python doesn't even look at the right side.or: If the left side isTrue, the whole expression isTrue— 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
andcondition isFalse, 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) > 0evaluates toFalse(the list is empty), so Python short-circuits theandand never evaluatesvalues[0] > 10. Without short-circuiting,values[0]would raise anIndexError.
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-elsechains 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 withand/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.
Menu Selection Example
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/caseas a switchboard operator routing a call. You give them a number, and they connect you to the right line. Think ofif-elif-elseas 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 = 5means "x becomes 5"). Double==means "equals?" (comparison:x == 5means "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, sotempis a string like"105". Comparing a string to an integer with>raises aTypeError. The fix:temp = int(input("Temperature: "))ortemp = 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:
-
Empty name check (
if task_name == ""): Uses a simpleif-elseto validate the task name. The.strip()call removes leading/trailing whitespace, so" "is treated as empty. -
Priority validation (
if priority != "high" and priority != "medium" and priority != "low"): Usesandwithnot-equalchecks. 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. -
Priority-based formatting (
if/elif/else): A three-way branch that sets the marker and label based on priority. This is a classicelifuse 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.