Skip to main content

🔀 Conditional Statements (if, else, switch)

Programs become useful the moment they can make choices. In this lesson you'll teach JavaScript to branch — running one block of code when a condition holds and another when it doesn't — using if, else if, else, switch, and the ternary operator, and you'll learn the truthy/falsy rules that quietly power every decision.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Write if, if…else, and if…else if…else chains to branch on one or many conditions
  • Predict how JavaScript coerces any value to a boolean using the truthy/falsy rules
  • Choose between a switch statement and an if-chain, and use fall-through deliberately
  • Use the ternary operator for concise value selection without overusing it
  • Flatten deeply nested logic with guard clauses, early returns, and object lookups

Estimated Time: 30–40 minutes  •  Difficulty: Beginner

Hands-on: Build a grade-and-fee calculator that refactors a messy if-chain into clean, testable branches.

In This Lesson

Why Programs Need Decisions

Without conditionals, a program can only do one fixed thing every time it runs. Conditionals let code respond: show an error if a form field is empty, apply free shipping over a threshold, greet a user by name only when they're logged in. A conditional statement evaluates a condition — an expression that resolves to true or false — and runs different code depending on the result.

💡 Analogy — a road network. An if is a gate that only opens when a condition is met. An if…else is a fork where you must pick exactly one path. An if…else if…else is a signposted junction where you take the first route you qualify for. A switch is a roundabout with clearly labelled exits, and the ternary is a quick two-way shortcut.

JavaScript gives you five tools for branching. Here's the whole family before we take each one apart:

flowchart TD A[Conditional tools in JavaScript] A --> B["if — run a block when true"] A --> C["if…else — pick one of two blocks"] A --> D["if…else if…else — first match wins"] A --> E["switch — compare one value to many cases"] A --> F["ternary — choose between two values"]

The if Statement

The if statement runs a block of code only when its condition is truthy. If the condition is falsy, the block is skipped entirely and execution continues after it.

const age = 18;

if (age >= 18) {
  console.log("You are an adult!");
}
Flow of a single if statement Execution reaches a condition; if true it runs the block, if false it skips the block, and both paths continue afterwards. start condition true? run the block continue yes no
Figure 1 — A single if: the block runs only on the "true" branch; the "false" branch skips straight to the code that follows.

📖 Condition vs. statement

Condition: the expression in the parentheses (age >= 18). It is evaluated and coerced to a boolean.

Block: the code inside { }. Always use braces, even for one line — it prevents a whole class of bugs when you add a second statement later.

Truthy & Falsy Values

A condition doesn't have to be a boolean. JavaScript will coerce any value to true or false. Knowing the exact list of falsy values is one of the highest-leverage things you can memorise as a beginner, because it explains a huge share of "why didn't my if run?" surprises.

⚠️ The complete list of falsy values

Only these eight values are falsy. Everything else is truthy.

false  ·  0  ·  -0  ·  0n (BigInt zero)  ·  "" (empty string)  ·  null  ·  undefined  ·  NaN

The tricky ones are the values that feel empty but are actually truthy:

if ([])            console.log("An empty array is truthy");   // runs
if ({})            console.log("An empty object is truthy");  // runs
if ("false")       console.log("The string 'false' is truthy"); // runs
if ("0")           console.log("The string '0' is truthy");   // runs

if ("")            console.log("never runs");  // empty string is falsy
if (0)             console.log("never runs");  // zero is falsy
if (null)          console.log("never runs");  // null is falsy

⚠️ The 0 trap

Checking if (user.age) to mean "does age exist?" silently fails when the age is legitimately 0. Prefer an explicit test such as if (user.age !== undefined) or if (user.age != null) when zero is a valid value.

if…else and else if Chains

Add an else to run alternative code when the condition is falsy — exactly one of the two blocks always runs. Add else if to test further conditions in order; the first one that is truthy wins, and the rest are skipped.

function letterGrade(score) {
  if (score >= 90) return "A";
  else if (score >= 80) return "B";
  else if (score >= 70) return "C";
  else if (score >= 60) return "D";
  else return "F";
}

console.log(letterGrade(85)); // "B"

Order matters because evaluation stops at the first match. If you reversed the tests to start with score >= 60, every passing score would be graded "D" — the more specific conditions would never be reached.

flowchart TD A[score] --> B{score >= 90?} B -->|yes| BA["A"] B -->|no| C{score >= 80?} C -->|yes| CA["B"] C -->|no| D{score >= 70?} D -->|yes| DA["C"] D -->|no| E["D or F"]

✅ Real-world example: form validation

Validation is a classic if-chain: return the first problem you find, so the user fixes one thing at a time.

function validateUsername(username) {
  if (!username) return "Username is required.";
  if (username.length < 3) return "Username must be at least 3 characters.";
  if (!/^[a-zA-Z0-9_]+$/.test(username)) {
    return "Only letters, numbers, and underscores are allowed.";
  }
  return null; // null means "valid"
}

const error = validateUsername("jo");
if (error) console.log(`Error: ${error}`);
else console.log("Username is valid!");

The switch Statement

When you're comparing one value against many discrete possibilities, a switch can read more cleanly than a long if-chain. The expression is evaluated once, then compared to each case using strict equality (===). A break ends the matched case; the optional default handles no-match.

function nextTrafficLight(current) {
  switch (current) {
    case "green":  return "yellow";
    case "yellow": return "red";
    case "red":    return "green";
    default:       return "unknown";
  }
}

console.log(nextTrafficLight("green")); // "yellow"

⚠️ Fall-through: powerful but easy to trip over

Without a break (or return), execution "falls through" into the next case. This is a bug most of the time — but it's genuinely useful for grouping cases that share code:

function seasonOf(month) {
  switch (month) {
    case 11: case 0: case 1:  return "Winter";
    case 2:  case 3: case 4:  return "Spring";
    case 5:  case 6: case 7:  return "Summer";
    case 8:  case 9: case 10: return "Fall";
    default: return "Invalid month";
  }
}

📖 Modern alternative: object lookup

For simple value-to-value mapping, an object (or Map) is often cleaner than a switch and easier to extend:

const nextLight = { green: "yellow", yellow: "red", red: "green" };
const next = nextLight[current] ?? "unknown";

Reach for switch when cases need multiple statements or fall-through; reach for an object when each case is just a returned value.

The Ternary Operator

The conditional (ternary) operator is a compact expression that produces a value: condition ? valueIfTrue : valueIfFalse. Because it's an expression, it fits where a full if statement can't — inside a template literal, a JSX attribute, or a variable assignment.

const age = 20;
const status = age >= 18 ? "Adult" : "Minor";

const greeting = `Good ${new Date().getHours() < 12 ? "morning" : "afternoon"}`;

⚠️ Don't nest ternaries deeply

A one-level ternary is elegant. Stacking three or four to imitate an if-chain hurts readability fast. When you feel the urge, use an if-chain or an early-return function instead:

// Hard to read — avoid
const grade = s >= 90 ? "A" : s >= 80 ? "B" : s >= 70 ? "C" : "F";

// Clearer
function grade(s) {
  if (s >= 90) return "A";
  if (s >= 80) return "B";
  if (s >= 70) return "C";
  return "F";
}

Flattening Nested Logic

Deeply nested if statements — the dreaded "arrow code" that drifts ever rightward — are hard to read and harder to modify. Three techniques keep branching flat.

1. Guard clauses (early return)

Check the failure cases first and return immediately, so the happy path stays at the left margin, unindented.

// Nested and hard to follow
function processPayment(amount, user) {
  if (amount > 0) {
    if (user.account) {
      if (user.account.balance >= amount) {
        user.account.balance -= amount;
        return "Payment successful";
      } else return "Insufficient funds";
    } else return "No account found";
  } else return "Invalid amount";
}

// Flat, with guard clauses
function processPaymentBetter(amount, user) {
  if (amount <= 0) return "Invalid amount";
  if (!user.account) return "No account found";
  if (user.account.balance < amount) return "Insufficient funds";

  user.account.balance -= amount;
  return "Payment successful";
}

2. Combine with logical operators

// Instead of three nested ifs
if (isLoggedIn && hasPermission && resourceExists) {
  accessResource();
}

3. Optional chaining for safe access

// Risky: throws if profile is undefined
if (user.profile.name === "Admin") { /* ... */ }

// Safe with optional chaining (ES2020)
if (user?.profile?.name === "Admin") { /* ... */ }

💡 Prefer === over ==

Strict equality (===) compares without type coercion, so 0 === "0" is false. Loose equality (==) coerces first and produces surprising results. Default to === unless you have a specific reason not to.

Hands-on Exercise

🏋️ Build a Ticket Price Calculator

Objective: Combine an if-chain, a switch, and guard clauses in one small function.

Instructions:

  1. Write ticketPrice(age, dayType) where dayType is "weekday", "weekend", or "holiday".
  2. Start with a guard clause: if age is negative or not a number, return the string "Invalid age".
  3. Set a base price with an if-chain: under 5 → free (0); under 18 → 8; 65 and over → 10; everyone else → 15.
  4. Apply a surcharge with a switch on dayType: weekend adds 3, holiday adds 5, weekday adds 0.
  5. Return the final number. Test ticketPrice(10, "weekend") → 11 and ticketPrice(70, "holiday") → 15.
💡 Hint

Keep the base price in a let so the switch can add to it. Free tickets (age under 5) can stay free — but the exercise above still applies the surcharge; decide whether that's the behaviour you want and note it. Use Number.isFinite(age) for the guard.

✅ Sample solution
function ticketPrice(age, dayType) {
  // Guard clause
  if (!Number.isFinite(age) || age < 0) return "Invalid age";

  // Base price by age (first match wins)
  let price;
  if (age < 5) price = 0;
  else if (age < 18) price = 8;
  else if (age >= 65) price = 10;
  else price = 15;

  // Day-type surcharge
  switch (dayType) {
    case "weekend": price += 3; break;
    case "holiday": price += 5; break;
    case "weekday": break; // no surcharge
    default: return "Invalid day type";
  }

  return price;
}

console.log(ticketPrice(10, "weekend")); // 11
console.log(ticketPrice(70, "holiday")); // 15
console.log(ticketPrice(-1, "weekday")); // "Invalid age"

🎯 Quick Quiz

Question 1: Which of these values is truthy in JavaScript?

Question 2: In a switch, what happens to a matched case that has no break or return?

Question 3: Why do guard clauses (early returns) improve a function?

Summary & Quiz

🎉 Key Takeaways

  • if / else if / else branch on one or many conditions; the first truthy test wins and stops the chain.
  • Only eight values are falsyfalse, 0, -0, 0n, "", null, undefined, NaN. Everything else, including [] and {}, is truthy.
  • switch compares one value to many cases with ===; remember break, and use fall-through only on purpose.
  • The ternary produces a value inline — great for simple choices, poor for deep nesting.
  • Guard clauses, logical operators, optional chaining, and object lookups keep branching logic flat and readable.

📚 Further Reading

🚀 What's Next?

Now that your code can choose a path, the next step is teaching it to repeat work. Up next: Loops (for, while, do-while) — how to run a block many times, count through arrays, and stop safely.

🎉 Well done!

You can make JavaScript decide. Let's make it repeat.