SECTION 1
Historical Context & Motivation
Since the earliest days of programming, conditional bugs have been among the most common and frustrating errors developers face. A conditional bug is a mistake inside an if statement or comparison that causes your program to follow the wrong path. These errors don't usually crash your program outright — instead, they produce subtly wrong results that can be incredibly hard to track down.
The history of conditional bugs is intertwined with the history of software itself. As programs grew from a few lines of machine code to millions of lines of JavaScript, the opportunities for logic errors multiplied. Several landmark moments in computing history demonstrate just how costly a single misplaced condition can be.
1962 • Mariner 1 — The Missing Hyphen
1996 • Ariane 5 — Boundary Overflow
1999 • Y2K — The Two-Digit Year Bug
2015 • JavaScript ES6 Strict Equality Push
These examples share a common thread: each disaster originated from a small, easily overlooked mistake in how a condition was written or how a boundary was handled. The central question this lesson addresses is straightforward yet vital — how do you write conditionals that behave correctly in every situation, including the tricky edge cases?
SECTION 2
Core Principles of Bug-Free Conditionals
Writing reliable conditionals comes down to understanding a handful of foundational principles. These ideas apply no matter what you're building — from a simple grade calculator to a complex web app. Mastering them will dramatically reduce the time you spend hunting for mysterious bugs.
Use Strict Equality
=== and !== over == and !=. Strict equality checks both value and type, preventing unexpected type coercion surprises.Order Conditions Carefully
if / else if / else chains. If a general condition comes first, it may catch values meant for a more specific branch.Check Boundary Values
null, undefined, negative numbers, and the maximum length of an array. Always ask: what happens at the boundaries?Beware Off-by-One Errors
< when you mean <= (or vice versa) is one of the most common bugs in all of programming. Always think about whether the boundary value itself should be included.Handle Falsy Values Explicitly
false, 0, "", null, undefined, and NaN. If your condition relies on truthiness, make sure you aren't accidentally excluding valid values like 0.KEY TAKEAWAY
SECTION 3
Visual Explanation — Condition Ordering
The diagram below illustrates a classic condition-ordering bug. Imagine a grading function that assigns letter grades based on a numeric score. On the left side, the conditions are in the wrong order — the broadest check comes first and catches everything. On the right side, the conditions are in the correct order — from most specific to most general.
score >= 60 is checked first. Because 95 is indeed greater than or equal to 60, the program assigns "D" and never checks the more specific conditions below. On the right, checking score >= 90 first ensures the highest grade is assigned correctly.This pattern is the single most common conditional ordering bug. JavaScript evaluates if / else if chains from top to bottom, and it stops at the first true condition. If a broader condition appears before a narrower one, the narrower condition becomes unreachable dead code. The fix is simple: always arrange your conditions from most restrictive to least restrictive.
SECTION 4
How Conditional Bugs Work Under the Hood
Type Coercion and Loose Equality
One of JavaScript's most infamous quirks is type coercion — the engine's automatic conversion of values from one type to another when you use the loose equality operator ==. This means 0 == "" evaluates to true because JavaScript converts the empty string to the number 0 before comparing. This hidden conversion is the root of many conditional bugs.
LOOSE EQUALITY COERCION
== converts types before comparing. === checks both value AND type — no conversion. Always prefer ===.
The Falsy Value Trap
When you write if (value), JavaScript checks whether value is truthy. Six values are considered falsy — they behave like false in a conditional. The problem arises when a valid value like 0 or an empty string "" is falsy. For instance, if you check if (age) to see if a user entered their age, a newborn baby with age 0 would be incorrectly treated as "no input."
JAVASCRIPT'S SIX FALSY VALUES
Every other value is truthy, including "0" (the string zero), [] (empty array), and {} (empty object).
Off-by-One Errors: < vs <=
An off-by-one error (often abbreviated OBOE) occurs when your condition is one step too narrow or one step too wide. Consider a loop that processes array elements: if the array has 5 items at indices 0 through 4, using i <= 5 as the condition tries to access index 5, which doesn't exist. Using i < 5 or i < arr.length is correct. This is a boundary condition bug, and it appears constantly in real-world code.
ARRAY BOUNDARY RULE
An array of length n has valid indices from 0 to n − 1. Use < n (not <= n) to stay in bounds.
SECTION 5
Catalog of Common Conditional Bugs
Let's organize the most frequent conditional bugs into a visual reference. The diagram below maps each bug type to a code snippet showing the mistake and the fix. Think of this as a "field guide" for debugging conditionals.
| Bug Type | What Goes Wrong | How to Prevent It |
|---|---|---|
| Assignment as condition | Using = instead of === assigns a value and always evaluates truthy (if non-zero) | Enable ESLint's no-cond-assign rule and always use === |
| Loose equality coercion | Values of different types are silently converted, producing unexpected matches like "" == false | Always use strict equality: === and !== |
| Off-by-one (OBOE) | Loop runs one time too many or too few, accessing invalid array indices or skipping the last element | Use < array.length for loops. Test with arrays of length 0 and 1 |
| Wrong condition order | A general condition overshadows specific ones, making later branches unreachable dead code | Sort conditions from most restrictive to least restrictive in if / else if chains |
| Falsy value trap | Valid values like 0 or "" are excluded because they're falsy | Compare explicitly: value !== null && value !== undefined |
Summary of the five most common conditional bug types
SECTION 6
Worked Example — Debugging a Ticket Price Function
Let's walk through a realistic example. A theme park sells tickets at different prices based on age. The original code has several conditional bugs. We'll identify each one and fix it step by step.
BUGGY CODE
function getTicketPrice(age) {
if (age >= 3) {
return "$15 — Child";
} else if (age >= 13) {
return "$25 — Teen";
} else if (age == 65) {
return "$10 — Senior";
} else {
return "Free — Infant";
}
}Finding and Fixing the Bugs
- 1
Step 1 — Identify the Ordering Bug
The first conditionage >= 3is too broad. It catches anyone age 3 and older — which includes teens (13+) and seniors (65+). The teen and senior branches are unreachable. We need to check from the highest age group down to the lowest.Bug #1: Wrong condition order — broad condition overshadows specific ones - 2
Step 2 — Identify the Loose Equality Bug
The senior condition usesage == 65with loose equality. Ifagearrives as the string"65"(for example, from a form input), loose equality would match — but that might mask a deeper data issue. Also, the condition only matches exactly 65, not 65 and older.Bug #2: Loose equality + wrong boundary — should be age >= 65 with === - 3
Step 3 — Consider the Boundary at Age 0
Infants (age 0, 1, or 2) should get in free. The currentelsecatches them, but only because the broader bug exists. After fixing the order, we also need to handle negative ages — what if someone passes-1? We should add input validation.Bug #3: Missing boundary validation — negative ages are not handled - 4
Step 4 — Write the Fixed Version
We reorder conditions from most specific to most general, use strict equality operators for comparison, ensure the senior discount applies to everyone 65 and older, and add input validation at the top.function getTicketPrice(age) { if (typeof age !== "number" || age < 0) { return "Invalid age"; } else if (age >= 65) { return "$10 — Senior"; } else if (age >= 13) { return "$25 — Teen"; } else if (age >= 3) { return "$15 — Child"; } else { return "Free — Infant"; } } - 5
Step 5 — Test with Boundary Values
We should test with ages at every boundary:-1(invalid),0(infant),2(infant edge),3(child edge),12(child edge),13(teen edge),64(adult edge),65(senior edge), and100(well into senior range).All boundary values produce the correct ticket price — the bugs are fixed!
SECTION 7
Comparison of Debugging Strategies
There are several strategies for preventing and catching conditional bugs. Each has strengths and limitations. Understanding when to use each approach will make you a much more effective debugger.
| Strategy | Strengths | Limitations |
|---|---|---|
| Linting (ESLint) | Catches bugs automatically before code runs; enforces strict equality and warns about assignment in conditions | Cannot detect logic ordering errors or domain-specific boundary issues |
| Console.log debugging | Quick and easy; lets you see exactly which branch executes for a given input | Time-consuming for complex chains; you must remove logs afterward; not systematic |
| Boundary value testing | Systematically tests the exact values where bugs hide; catches off-by-one and edge-case errors | Requires you to identify all boundaries upfront; takes planning effort |
| Truth tables | Exhaustively maps every input to its expected output; great for complex Boolean logic | Impractical for large input ranges; works best for a small number of Boolean flags |
| Unit testing (Jest, etc.) | Permanently documents expected behavior; reruns automatically to catch regressions | Requires additional setup and tooling; tests are only as good as the cases you write |
Comparison of strategies for preventing conditional bugs
KEY TAKEAWAY
SECTION 8
Connection to Advanced Patterns
The principles you've learned here scale up as your code grows more complex. In advanced programming, developers use design patterns and language features to reduce the chance of conditional bugs in the first place. Understanding the connection between beginner techniques and professional patterns gives you a clear path forward.
| What You Know Now | Advanced Pattern | Why It Helps |
|---|---|---|
Using === for strict comparison | TypeScript — a typed superset of JavaScript | TypeScript catches type mismatches at compile time, eliminating entire categories of coercion bugs before your code ever runs |
Ordering if / else if chains carefully | Switch statements and object lookups | When each condition maps a specific value to an action, a switch or lookup table eliminates ordering bugs entirely since each case is independent |
| Boundary value testing | Property-based testing (e.g., fast-check) | Instead of manually choosing boundary values, the tool generates hundreds of random inputs — including edge cases you might never think of |
Checking for null / undefined | Optional chaining (?.) and nullish coalescing (??) | These modern JS operators let you safely handle null/undefined in a single expression, reducing the number of conditionals you need |
From beginner techniques to advanced patterns
As you advance in programming, you'll find that the best way to avoid conditional bugs is often to write fewer conditionals. Techniques like polymorphism in object-oriented programming and pattern matching in functional programming replace long if-else chains with cleaner structures. But the debugging instincts you build now — checking boundaries, questioning equality operators, thinking about condition order — will serve you throughout your entire career.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
if (score >= 50) placed before else if (score >= 90) is a bug. What will happen when score is 95?Show answer
score is 95, JavaScript evaluates score >= 50 first. Since 95 is indeed greater than or equal to 50, this condition is true, and the program enters that branch. It never reaches the score >= 90 check because JavaScript stops at the first true condition in an if/else-if chain. This is a condition-ordering bug. The fix is to place the most restrictive check (>= 90) first so high scores are caught before the broader condition.Question 2: BASIC CALCULATION
"" == false Now compare it to "" === false. Explain the difference.Show answer
"" == false evaluates to true. The loose equality operator converts both values to numbers before comparing: the empty string becomes 0 and false becomes 0, so 0 equals 0. However, "" === false evaluates to false because strict equality checks both value and type — a string is not the same type as a Boolean. This demonstrates why strict equality prevents type-coercion bugs: it refuses to convert types silently.Question 3: INTERMEDIATE
function lastElement(arr) {
if (arr.length) {
return arr[arr.length];
}
return "Array is empty";
}Show answer
arr[arr.length]. Array indices start at 0, so an array of length 5 has valid indices 0 through 4. Accessing arr[5] returns undefined. The fix is to use arr[arr.length - 1]. Additionally, the if (arr.length) guard works here because length 0 is falsy, but a clearer way to write it would be if (arr.length > 0) to make the intent explicit.Question 4: APPLIED
getDiscount(total) that returns the correct discount percentage. Make sure you avoid condition-ordering bugs and handle the boundary value of exactly $100, $200, and $500. What should the function return for a total of $0 or a negative number?Show answer
total >= 500 first, then total >= 200, then total >= 100. For boundary values: $100 should get exactly 10%, $200 exactly 20%, $500 exactly 30%. A correct implementation:
function getDiscount(total) {
if (typeof total !== 'number' || total < 0) return 0;
if (total >= 500) return 30;
if (total >= 200) return 20;
if (total >= 100) return 10;
return 0;
}
The input validation at the top handles negative numbers and non-numeric input by returning 0% discount, preventing unexpected behavior.Question 5: CRITICAL THINKING
if (userInput) to check whether the user has entered a value in a form field. Identify at least three specific situations where this check would produce incorrect results. Then propose a more robust way to validate the input that avoids all three issues. Justify your solution.Show answer
0 as a valid number — this is falsy, so the check treats it as "no input." (2) The user submits an empty string "" — correctly falsy, but if whitespace-only strings like " " are entered, they're truthy even though they contain no real content. (3) The value is null or undefined — falsy, but the developer may want to distinguish between "not provided" and "provided but empty." A robust solution: if (userInput !== null && userInput !== undefined && String(userInput).trim() !== ''). This explicitly checks for null/undefined, converts to string to handle numeric inputs safely, and trims whitespace. This approach avoids the falsy-value trap while still filtering out genuinely empty input.SUMMARY
Lesson Summary
Summary
Conditional bugs are among the most common errors in JavaScript. They arise from several key mistakes: using loose equality (==) instead of strict equality (===), placing conditions in the wrong order so that broad checks overshadow specific ones, making off-by-one errors at array boundaries, accidentally using assignment (=) instead of comparison (===) in conditions, and falling into the falsy value trap where valid values like 0 or empty strings are incorrectly excluded.
To prevent these bugs, always order conditions from most specific to most general, use strict equality operators, test boundary values (including 0, empty strings, null, and the edges of ranges), and use linting tools to catch mechanical errors automatically. As you grow as a developer, these habits will become second nature, and you'll progress to advanced patterns like TypeScript, optional chaining, and pattern matching that build on the same foundations.