JAVASCRIPT • CONTROL FLOW

Avoiding Conditional Bugs — Avoid common conditional bugs (ordering, boundary conditions)

Learn to spot and fix the sneaky logic errors that hide inside your if-statements and comparisons.

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
NASA's Mariner 1 rocket was destroyed 293 seconds after launch. A tiny punctuation error in the guidance software caused the rocket to veer off course — one of the earliest high-profile software bugs.
1996
Ariane 5 — Boundary Overflow
The European Space Agency's Ariane 5 rocket exploded because a number exceeded the boundary of a 16-bit integer. The software tried to fit a value too large for its container, producing a catastrophic error.
1999
Y2K — The Two-Digit Year Bug
Programs that stored years as two digits (99 instead of 1999) faced boundary conditions when the year 2000 arrived. Billions of dollars were spent fixing conditions that couldn't handle the rollover.
2015
JavaScript ES6 Strict Equality Push
Modern JavaScript linting tools began strongly recommending strict equality (===) over loose equality (==) to prevent type-coercion bugs. This addressed one of the most common conditional pitfalls in JS development.

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?

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.

1

Use Strict Equality

Always prefer === and !== over == and !=. Strict equality checks both value and type, preventing unexpected type coercion surprises.
2

Order Conditions Carefully

Place the most specific conditions first and the most general conditions last in your if / else if / else chains. If a general condition comes first, it may catch values meant for a more specific branch.
3

Check Boundary Values

Bugs often lurk at the edges: 0, empty strings, null, undefined, negative numbers, and the maximum length of an array. Always ask: what happens at the boundaries?
4

Beware Off-by-One Errors

Using < 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.
5

Handle Falsy Values Explicitly

JavaScript has six falsy values: 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
Think of a conditional like a series of doors in a hallway. If you put the widest door first, almost everyone will walk through it — even people who were supposed to go through a smaller, specialized door further down. Place the most specific condition first so each case is directed to the correct door before hitting the catch-all at the end.

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.

On the left, the condition 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.

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
0 == "" → true | 0 === "" → false
== 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
false, 0, "" (empty string), null, undefined, NaN
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
Valid indices: 0 to array.length − 1
An array of length n has valid indices from 0 to n − 1. Use < n (not <= n) to stay in bounds.

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.

Five of the most common conditional bug types in JavaScript. The left column shows the buggy code, and the right column shows the corrected version. Notice how each fix involves a small but critical change.
Summary of the five most common conditional bug types
Bug TypeWhat Goes WrongHow to Prevent It
Assignment as conditionUsing = instead of === assigns a value and always evaluates truthy (if non-zero)Enable ESLint's no-cond-assign rule and always use ===
Loose equality coercionValues of different types are silently converted, producing unexpected matches like "" == falseAlways 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 elementUse < array.length for loops. Test with arrays of length 0 and 1
Wrong condition orderA general condition overshadows specific ones, making later branches unreachable dead codeSort conditions from most restrictive to least restrictive in if / else if chains
Falsy value trapValid values like 0 or "" are excluded because they're falsyCompare explicitly: value !== null && value !== undefined

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 BugThe first condition age >= 3 is 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 BugThe senior condition uses age == 65 with loose equality. If age arrives 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 0Infants (age 0, 1, or 2) should get in free. The current else catches 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 VersionWe 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 ValuesWe 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), and 100 (well into senior range).
All boundary values produce the correct ticket price — the bugs are fixed!

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.

Comparison of strategies for preventing conditional bugs
StrategyStrengthsLimitations
Linting (ESLint)Catches bugs automatically before code runs; enforces strict equality and warns about assignment in conditionsCannot detect logic ordering errors or domain-specific boundary issues
Console.log debuggingQuick and easy; lets you see exactly which branch executes for a given inputTime-consuming for complex chains; you must remove logs afterward; not systematic
Boundary value testingSystematically tests the exact values where bugs hide; catches off-by-one and edge-case errorsRequires you to identify all boundaries upfront; takes planning effort
Truth tablesExhaustively maps every input to its expected output; great for complex Boolean logicImpractical 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 regressionsRequires additional setup and tooling; tests are only as good as the cases you write
✦ KEY TAKEAWAY
No single strategy catches every bug. Think of it like car safety: a seatbelt (linting) protects against common accidents, airbags (testing) handle bigger crashes, and mirrors (console.log) help you see what's happening in real time. Use multiple strategies together for the best protection.

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.

From beginner techniques to advanced patterns
What You Know NowAdvanced PatternWhy It Helps
Using === for strict comparisonTypeScript — a typed superset of JavaScriptTypeScript catches type mismatches at compile time, eliminating entire categories of coercion bugs before your code ever runs
Ordering if / else if chains carefullySwitch statements and object lookupsWhen 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 testingProperty-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 / undefinedOptional 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

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.

Practice Problems

PROBLEM 1 — CONCEPTUAL
Explain in your own words why if (score >= 50) placed before else if (score >= 90) is a bug. What will happen when score is 95?
PROBLEM 2 — BASIC CALCULATION
What does the following expression evaluate to, and why? "" == false Now compare it to "" === false. Explain the difference.
PROBLEM 3 — INTERMEDIATE
The following function is supposed to return the last element of an array, but it has a bug. Find and fix it: function lastElement(arr) { if (arr.length) { return arr[arr.length]; } return "Array is empty"; }
PROBLEM 4 — APPLIED
A shopping cart applies discounts based on the total: orders of $100 or more get 10% off, orders of $200 or more get 20% off, and orders of $500 or more get 30% off. Write a function 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?
PROBLEM 5 — CRITICAL THINKING
A developer writes 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.

Lesson 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.

Varsity Tutors • JavaScript • Avoiding Conditional Bugs — Avoid common conditional bugs (ordering, boundary conditions)