Python • Control Flow

Avoiding Conditional Bugs — Avoid common conditional bugs (order of conditions, boundary checks)

Learn to spot and fix the sneaky logic errors that hide inside your if-elif-else chains.

Code braces with terminal and markup symbols representing Python programming.
Inside this lesson 10 sections 5 questions 5 figures

SECTION 1

Historical Context & Motivation

Since the earliest days of programming, conditional logic bugs have been one of the most common sources of software errors. When a program needs to make a decision — "Is the user old enough?" or "Did the student pass?" — even a small mistake in how the conditions are written can cause the wrong branch to execute. These bugs are tricky because the code often looks correct at first glance, yet produces wrong results for certain inputs.

Throughout computing history, conditional bugs have caused spectacular failures — from rockets exploding due to incorrect boundary checks to banking software miscalculating interest. Understanding these bugs isn't just an academic exercise; it's a practical survival skill for any programmer.

1947 • The First Computer Bug

Grace Hopper's team found an actual moth stuck in the Harvard Mark II relay, coining the term "debugging." Logic errors in early programs were already a major concern.

1996 • Ariane 5 Rocket Explosion

A software condition failed to handle a value that exceeded its expected boundary. The rocket self-destructed 37 seconds after launch, costing $370 million.

1999 • Y2K Bug Panic

Programs stored years as two digits, so the year 2000 was treated as 1900. This was essentially a boundary check failure on date conditions.

2014 • Heartbleed Vulnerability

A missing boundary check in OpenSSL allowed attackers to read sensitive server memory. This single conditional bug affected millions of websites worldwide.

These real-world disasters all share a common theme: a programmer wrote a conditional statement that didn't account for every possible input. The question this lesson addresses is straightforward — how do you write conditions that work correctly for every possible case, including the tricky edge cases?

SECTION 2

Core Principles of Conditional Bug Prevention

Before diving into specific bugs, it helps to understand the foundational principles that keep your conditionals reliable. Each principle targets a different way that conditions can go wrong, from testing conditions in the wrong order to forgetting about values that sit right on the boundary between two categories.

1

Condition Order Matters

In an if-elif chain, Python checks conditions from top to bottom and executes the first match. If a broad condition comes before a specific one, the specific case will never run.
2

Boundary Checks

Values at the exact edge of a range (like scoring exactly 90) are the most error-prone. Always ask: should the boundary value be included (>=) or excluded (>)?
3

Overlapping Conditions

When two conditions in an if-elif chain can both be true for the same input, only the first one runs. This creates hidden bugs when the second condition was the intended match.
4

Missing Else / Default Case

If your conditions don't cover every possible input, some values fall through without being handled. Always consider whether you need a final else clause as a safety net.
5

Off-by-One Errors

These occur when you use > instead of >=, or vice versa. They're called "off-by-one" because they misclassify exactly one boundary value.
✦

KEY TAKEAWAY

Think of conditional logic like a series of doors in a hallway. A person walks down the hall and enters the first open door they find. If you put the "VIP Lounge" door after the "General Admission" door, and both doors are open to VIPs, every VIP ends up in General Admission. Order your doors from most specific to most general so that special cases are caught before broad ones swallow them up.

SECTION 3

Visual Explanation — Condition Order Flowchart

The following diagram shows what happens when conditions are placed in the wrong order versus the correct order. Imagine a grading function that assigns letter grades based on a student's score. On the left, the broad condition score >= 60 appears first, catching students who scored 90 or above before the score >= 90 check ever runs. On the right, the conditions are ordered from highest threshold to lowest, so each grade is correctly assigned.

❌ WRONG ORDERscore >= 60 ?Yesgrade = "D"A score of 95 lands here! 😱score >= 90 ?Never reached for 95!BUG: 95 → "D" instead of "A"✅ CORRECT ORDERscore >= 90 ?Yes"A"Noscore >= 80 ?Yes"B"Noscore >= 70 ?Yes"C"Noscore >= 60 ?Yes"D"No"F"Specific (highest) → General (lowest) prevents shadowing
The left side shows the wrong order — a broad condition catches values before the specific check runs. The right side shows the correct order, moving from the most restrictive threshold down to the least restrictive.

Notice how in the wrong-order version, a student who scores 95 hits the score >= 60 check first. Since 95 is indeed greater than or equal to 60, Python assigns grade "D" and skips all remaining elif branches. The fix is simple: always check the most specific (highest threshold) condition first, then work your way down to the most general.

SECTION 4

How Conditional Bugs Happen — Detailed Mechanisms

Bug Type 1: Condition Shadowing

Condition shadowing occurs when a broader condition appears before a narrower one in an if-elif chain. The broader condition "shadows" the specific one, making it unreachable. Think of it this way: if condition A is true whenever condition B is true (but not vice versa), then A must come after B in the chain. For example, x >= 60 is true for every value where x >= 90 is true, so x >= 90 must be checked first.

Bug Type 2: Off-by-One Boundary Errors

An off-by-one error happens when you use > instead of >= (or < instead of <=). Consider a rule that says "scores of 90 and above earn an A." Writing score > 90 instead of score >= 90 means a student with exactly 90 misses the A. That single misplaced character affects real outcomes.

Bug Type 3: Gaps and Overlaps

When conditions have gaps, certain inputs match no condition at all. For instance, checking score > 90 and then score < 90 leaves out score == 90 entirely. Conversely, overlaps occur when two conditions both match the same input. With if-elif chains, overlaps silently pick the first branch, which may not be what you intended.

Bug Type 4: Missing Default Case

A missing default case means your code has no else clause to catch unexpected inputs. If a variable holds a value you didn't anticipate — say a negative score or a string — the program might silently do nothing instead of flagging the problem. Always ask yourself: "What happens if none of my conditions are true?"

💡

Pro Tip: Test the Boundaries

Whenever you write a conditional, immediately test it with the exact boundary values. If your condition is score >= 90, test with 89, 90, and 91. These three values tell you whether your boundary is in the right place.

SECTION 5

Bug Gallery — Common Patterns and Fixes

Let's look at a visual catalog of the most common conditional bugs and their corrections. The diagram below shows four buggy code patterns on the left paired with their fixed versions on the right, along with annotations explaining exactly what changed.

Common Conditional Bugs & Fixes❌ BUGGY CODE✅ FIXED CODEBUG 1: Wrong Orderif score >= 60: grade = "D"elif score >= 90: # unreachable! grade = "A"FIX 1: Most specific firstif score >= 90: grade = "A"elif score >= 60: grade = "D"BUG 2: Off-by-Oneif age > 18: # 18 excluded! print("Can vote")An 18-year-old cannot vote here 🐛FIX 2: Use >= for inclusiveif age >= 18: print("Can vote")18-year-olds are now included ✅BUG 3: Gap in Conditionsif temp > 100: status = "boiling"elif temp < 100: status = "not boiling"# temp == 100 → status is unset! 🐛FIX 3: Use else to close the gapif temp > 100: status = "boiling"else: status = "not boiling"# 100 is now handled ✅BUG 4: Using = Instead of ==if choice = "yes": # SyntaxError! proceed()Python catches this, but some languages don't 🐛FIX 4: Use == for comparisonif choice == "yes": proceed()Double equals for comparison ✅Always test boundary values and trace through your code with sample inputs.
Four of the most common conditional bugs shown side-by-side with their fixes. Pay special attention to Bug 3 — gaps in conditions are the hardest to spot because the code runs without errors but silently skips certain inputs.
Bug TypeWhat Goes WrongHow to Fix It
Condition ShadowingA broad condition catches inputs before a specific condition can.Order conditions from most specific to most general.
Off-by-OneBoundary value is assigned to the wrong category (e.g., 90 gets a B instead of an A).Check whether boundary should use >= or >.
Gap in ConditionsA specific input matches no condition, leaving variables unset.Add an else clause or close gaps with >= and <.
= vs ==Assignment operator used instead of comparison. Python raises a SyntaxError.Always use == inside conditions, never =.

Quick Reference: Conditional Bug Types

SECTION 6

Worked Example — Debugging a Ticket Pricing Function

A movie theater charges different ticket prices based on age. Children (under 13) pay $8, teens (13–17) pay $10, adults (18–64) pay $14, and seniors (65 and up) pay $9. A student wrote the following buggy code. Let's trace through it, find the bugs, and fix them step by step.

Fix the Ticket Price Calculator

  1. 1

    Step 1 — Read the Buggy Code

    Here is the original code: if age < 18: price = 10 elif age < 65: price = 14 elif age < 13: price = 8 else: price = 9 Before running it, let's try to predict what happens for age = 10 (a child who should pay $8).
  2. 2

    Step 2 — Trace with age = 10

    Python evaluates age < 18 first. Since 10 < 18 is True, it sets price = 10 (teen price) and skips the rest. The child price of $8 is never reached.
    Bug found: condition shadowing — age < 18 catches all children before age < 13 is ever checked.
  3. 3

    Step 3 — Reorder Conditions (Most Specific First)

    The narrowest age range is children (under 13), so that check goes first. Next comes teens (13–17, handled by age < 18), then adults (18–64, handled by age < 65), and finally seniors in the else clause.
    New order: age < 13 → age < 18 → age < 65 → else
  4. 4

    Step 4 — Write the Fixed Code

    The corrected version: if age < 13: price = 8 # child elif age < 18: price = 10 # teen elif age < 65: price = 14 # adult else: price = 9 # senior
  5. 5

    Step 5 — Verify with Boundary Values

    Test with boundary values to confirm: • age = 12 → 12 < 13 is True → $8 ✅ • age = 13 → 13 < 13 is False, 13 < 18 is True → $10 ✅ • age = 17 → $10 ✅ • age = 18 → 18 < 13 False, 18 < 18 False, 18 < 65 True → $14 ✅ • age = 65 → all False until else → $9 ✅ Every boundary value now gets the correct price.
    All boundary tests pass. The bug is fixed!

SECTION 7

Debugging Strategies — Strengths and Limitations

Different strategies exist for catching conditional bugs. Some are quick and informal while others are more systematic. Knowing the strengths and limitations of each approach helps you choose the right tool at the right time.

StrategyStrengthsLimitations
Manual TracingForces you to think like the computer. Catches logic errors early. No tools needed.Slow for complex chains. Easy to miss cases if you don't pick the right test values.
Boundary TestingTargets the exact values where bugs are most likely. Very efficient for numeric conditions.Doesn't help as much with string or boolean conditions. You need to identify all boundaries first.
Print DebuggingQuick to add. Shows which branch actually executed. Works in any environment.Clutters your code. Must be cleaned up afterward. Can become overwhelming in large programs.
Truth TableExhaustive analysis for conditions with a small number of boolean variables. Guarantees completeness.Impractical for conditions with many variables or continuous ranges like scores.
Code ReviewFresh eyes catch bugs the author is blind to. Great for condition order and missing else clauses.Requires another person. Quality depends on the reviewer's experience.

Comparison of Conditional Bug Detection Strategies

✦

KEY TAKEAWAY

Think of debugging strategies like checking your answers on a math test. Manual tracing is like re-solving the problem from scratch. Boundary testing is like plugging your answer back in to see if it works. The best programmers use both — just like the best math students.

SECTION 8

Connection to Advanced Concepts

The skills you're building here with simple if-elif-else chains lay the groundwork for much more advanced programming concepts. As you grow as a programmer, you'll encounter these conditional patterns in increasingly sophisticated forms.

What You Learned NowWhere It Leads
Ordering conditions from specific to generalPattern matching in Python 3.10+ (match-case), where order also determines which pattern is matched first.
Boundary checking with >= vs >Input validation in web applications, where boundary checks prevent security vulnerabilities like buffer overflows.
Testing boundary values manuallyUnit testing frameworks (pytest, unittest) that automate boundary testing with hundreds of test cases.
Using else as a default caseException handling (try-except) for catching unexpected runtime errors that conditionals alone can't anticipate.

From Basic Conditionals to Advanced Programming

Professional developers also use tools called linters (like pylint and flake8) that automatically scan code for common bugs, including some types of conditional errors. As you move into AP Computer Science or college-level courses, automated testing and static analysis will become essential parts of your workflow.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

A programmer writes the following code: if x > 0: print("positive") elif x > 10: print("large positive") Explain why the message "large positive" will never be printed, no matter what value x has.
Show answer
This is a condition shadowing bug. The condition x > 10 can only be true when x > 0 is also true, because every number greater than 10 is also greater than 0. Since Python evaluates if-elif chains top-to-bottom and stops at the first true condition, the x > 0 branch always executes first for any positive number, including numbers greater than 10. The fix is to swap the order so x > 10 is checked before x > 0.

Question 2: BASIC CALCULATION

A store gives a 10% discount to customers who spend $50 or more. The following code has a bug: if total > 50: total = total * 0.9 What is the output if total = 50? What should the output be? Fix the code.
Show answer
When total = 50, the condition 50 > 50 evaluates to False, so no discount is applied. The customer pays the full $50. However, the rule says "$50 or more" should get the discount, so the correct output should be $50 × 0.9 = $45. The fix is to change > to >=: if total >= 50:. This is a classic off-by-one boundary error.

Question 3: INTERMEDIATE

Identify all bugs in the following shipping cost calculator and rewrite it correctly: # Free shipping for orders >= $100 # $5 shipping for orders >= $50 # $10 shipping for everything else if order_total >= 50: shipping = 5 elif order_total >= 100: shipping = 0 else: shipping = 10
Show answer
There is one main bug: condition shadowing. The condition order_total >= 50 comes before order_total >= 100. Since any order of $100 or more also satisfies >= 50, those orders are charged $5 shipping instead of getting free shipping. The fix is to check the most specific (highest threshold) first: if order_total >= 100: shipping = 0 / elif order_total >= 50: shipping = 5 / else: shipping = 10. Now an order of $150 correctly gets free shipping, a $75 order pays $5, and a $30 order pays $10.

Question 4: APPLIED

A theme park has these ride height requirements: under 36 inches cannot ride, 36–47 inches can ride with an adult, and 48 inches or taller can ride alone. Write a complete if-elif-else block that correctly prints the appropriate message for any height value. Then list which boundary values you would use to test your code and what output each should produce.
Show answer
The correct code is: if height < 36: print("Cannot ride") / elif height < 48: print("Ride with adult") / else: print("Ride alone"). The conditions are ordered from most restrictive to least restrictive. Testing boundary values: height = 35 → "Cannot ride" (just under minimum), height = 36 → "Ride with adult" (exact minimum for accompanied riding), height = 47 → "Ride with adult" (just under solo threshold), height = 48 → "Ride alone" (exact solo threshold), and height = 49 → "Ride alone" (above solo threshold). These five tests confirm that every boundary is handled correctly.

Question 5: CRITICAL THINKING

Consider a function that determines a student's scholarship level: GPA of 4.0 gets "Full Scholarship," 3.5–3.99 gets "Half Scholarship," 3.0–3.49 gets "Book Stipend," and below 3.0 gets "No Award." A classmate argues that using separate if statements (instead of if-elif-else) would be fine since the ranges don't overlap. Explain why this reasoning is flawed and what specific bug it could introduce.
Show answer
Using separate if statements instead of if-elif-else means every condition is evaluated independently. A student with a 4.0 GPA would match gpa == 4.0 (Full Scholarship) but would also satisfy gpa >= 3.5 (Half Scholarship) and gpa >= 3.0 (Book Stipend). The variable holding the award would be overwritten with each match, ending up with "Book Stipend" — the last one to execute. Even if you think ranges don't overlap, using separate if statements lets each condition execute independently, creating unintended overwrites. The if-elif-else chain guarantees that exactly one branch runs, which matches the intent of mutually exclusive categories.

SUMMARY

Lesson Summary

Summary

Conditional bugs are among the most common errors in programming, but they follow predictable patterns that you can learn to avoid. The key principles are: always order conditions from most specific to most general to prevent condition shadowing; carefully choose between > and >= to avoid off-by-one errors; ensure there are no gaps or overlaps in your conditions; and include an else clause as a safety net for unexpected inputs.

The most reliable debugging habit is boundary value testing — always test the exact values where your conditions switch from true to false. If your condition uses >= 90, test 89, 90, and 91. This simple practice catches the vast majority of conditional bugs before they ever reach your users. These foundational skills in defensive programming will serve you well as you move on to more advanced topics like pattern matching, unit testing, and exception handling.

Varsity Tutors • Python • Avoiding Conditional Bugs