JavaScript If Else

The if...else statement is how JavaScript programs make decisions. It lets your code run different blocks depending on whether a condition is true or false, which is the foundation of almost every non-trivial program. Without conditional logic, a script could only ever execute the same sequence of statements every single time, regardless of input. Mastering if...else is one of the very first real milestones in learning to program, and understanding its quirks well will save you from a huge number of bugs later on.

Overview / How It Works

An if statement tells JavaScript: “evaluate this condition, and if it is truthy, run this block of code.” You can extend it with an optional else block that runs when the condition is falsy, and with one or more else if blocks to test additional conditions in sequence. JavaScript evaluates these top to bottom and stops at the very first branch whose condition is true — it never checks the remaining conditions once a match is found, and if none of them match, the final else block (if present) runs as the fallback.

A critical detail is that the condition inside the parentheses does not have to be a strict true or false value. JavaScript coerces whatever value you give it into a boolean using its internal ToBoolean abstract operation. Every value in JavaScript is either “truthy” or “falsy” when evaluated in a boolean context. There are exactly six falsy values in the language: false, 0 (and -0), "" (empty string), null, undefined, and NaN. Every other value — including "0", "false" as strings, empty arrays [], and empty objects {} — is truthy. This trips up a lot of beginners because an empty array looks “empty” but is still truthy.

Under the hood, the JavaScript engine parses an if...else chain into a single conditional branch in its internal bytecode/execution plan. Each block you write, even a single statement without braces, creates its own lexical scope for let and const declarations (this is why a variable declared inside an if block with let is not visible outside of it). The engine evaluates the condition expression, converts the result with ToBoolean, and jumps to whichever block corresponds to the outcome, skipping every other branch entirely — conditions after a match are never evaluated, which matters if a later condition has side effects like a function call.

Syntax

if (condition1) {
  // runs when condition1 is truthy
} else if (condition2) {
  // runs when condition1 is falsy and condition2 is truthy
} else {
  // runs when none of the above conditions are truthy
}
  • condition1, condition2 — any expression; it is coerced to a boolean via ToBoolean.
  • { } — a block statement; every path can contain multiple statements. Braces are optional for a single statement but strongly recommended (see Common Mistakes).
  • else if — optional, and you can chain as many as you need; each is checked only if every prior condition was falsy.
  • else — optional; always the last branch and never has its own condition.

The Conditional (Ternary) Operator

For simple two-way branches that just assign a value, JavaScript offers a shorthand: the ternary operator condition ? valueIfTrue : valueIfFalse. It is an expression (it produces a value), whereas if...else is a statement (it does not). Use a ternary only when the logic is simple — nesting multiple ternaries hurts readability fast.

Examples

Example 1: A Basic If/Else

const age = 20;

if (age >= 18) {
  console.log("You are an adult.");
} else {
  console.log("You are a minor.");
}

Output:

You are an adult.

Here age >= 18 evaluates to the boolean true, so the first block runs and the else block is skipped entirely.

Example 2: Chaining With Else If

const score = 82;
let grade;

if (score >= 90) {
  grade = "A";
} else if (score >= 80) {
  grade = "B";
} else if (score >= 70) {
  grade = "C";
} else {
  grade = "F";
}

console.log(`Score: ${score} -> Grade: ${grade}`);

Output:

Score: 82 -> Grade: B

JavaScript checks score >= 90 first (false), then score >= 80 (true), assigns "B", and stops — it never even evaluates score >= 70. Order matters: if the branches were written smallest-to-largest instead, every score of 70 or higher would incorrectly match the first branch.

Example 3: Nested Conditionals and Truthiness

function checkLogin(username, password) {
  if (username) {
    if (password && password.length >= 8) {
      console.log("Login successful.");
    } else {
      console.log("Password must be at least 8 characters.");
    }
  } else {
    console.log("Username is required.");
  }
}

checkLogin("alice", "short");
checkLogin("", "somepassword");
checkLogin("bob", "longenoughpassword");

Output:

Password must be at least 8 characters.
Username is required.
Login successful.

This example nests one if...else inside another and relies on truthy/falsy coercion twice: an empty string "" is falsy, so checkLogin("", ...) falls into the outer else without ever checking the password. The && operator short-circuits too — if password is falsy (like undefined), password.length is never accessed, which avoids a TypeError.

Under the Hood: Step by Step

  • The engine evaluates the first condition expression and converts the result using ToBoolean (objects, non-empty strings, and non-zero numbers all become true).
  • If the result is true, it executes the associated block and then jumps straight past every remaining else if/else branch — it does not evaluate them at all.
  • If the result is false, it moves to the next condition (an else if, if present) and repeats the same evaluate-and-branch process.
  • If every condition is falsy and an else block exists, that block runs unconditionally; if there is no else, the engine simply continues on to whatever statement follows the whole chain.
  • Each { } block creates its own block scope, so a let or const declared inside one branch does not leak into sibling branches or the surrounding code.

Common Mistakes

Mistake 1: Using = Instead of ===

A single = inside a condition performs assignment, not comparison, and the expression evaluates to the assigned value itself (which then gets coerced to a boolean). This is valid JavaScript syntax, so it will not throw an error — it will just silently do the wrong thing.

let x = 0;

if (x = 5) {
  console.log("This runs because x = 5 is an assignment, and 5 is truthy.");
}

console.log(x);

Output:

This runs because x = 5 is an assignment, and 5 is truthy.
5

The fix is to always use === (or == when intentionally allowing type coercion) for comparisons:

let x = 0;

if (x === 5) {
  console.log("This will not run since x is 0.");
} else {
  console.log("x is not equal to 5.");
}

Output:

x is not equal to 5.

Mistake 2: Omitting Braces on Multi-Line Blocks

Without { }, only the single statement immediately after the condition belongs to the if. Indentation is purely cosmetic to JavaScript and does not define the block.

function logStatus(isActive) {
  if (isActive)
    console.log("Active");
    console.log("Checked status");
}

logStatus(false);

Output:

Checked status

Even though console.log("Checked status") looks like it is inside the if, it actually runs unconditionally every time because only the very next statement was attached to the condition. Always wrap blocks in braces, even single-line ones, to avoid this exact bug when the code is later edited.

Best Practices

  • Always use { } around if/else bodies, even for one-line statements, so future edits cannot silently detach code from the condition.
  • Use strict equality (===/!==) instead of loose equality (==/!=) unless you specifically need type coercion.
  • Order else if chains from most specific to least specific (e.g. highest threshold first) when ranges can overlap.
  • Prefer early returns in functions over deep nesting — return early on invalid input instead of wrapping the rest of the function body in an else.
  • Use the ternary operator only for short, single-value expressions; reach for full if...else once the logic involves multiple statements or side effects.
  • Be explicit with truthy/falsy checks — write if (value !== null) or if (value !== undefined) instead of a bare if (value) when zero, an empty string, or NaN are valid values you don’t want to accidentally treat as “missing.”
  • Avoid nesting more than two or three levels deep; extract nested conditionals into well-named helper functions to keep logic readable.

Practice Exercises

  • Exercise 1: Write a function describeNumber(n) that logs "positive", "negative", or "zero" depending on the sign of n, using an if...else if...else chain.
  • Exercise 2: Write a function canVote(age, isCitizen) that returns true only if age is 18 or older AND isCitizen is true, otherwise returns false. Try solving it both with nested if statements and with a single combined condition.
  • Exercise 3: Given a variable temperature, write conditional logic that logs "freezing" for 32°F or below, "cold" for 33–60°F, "mild" for 61–80°F, and "hot" above 80°F. Test it with at least four different temperature values and predict the output before running it.

Summary

  • if runs a block only when its condition is truthy; else provides a fallback for when it is falsy.
  • else if lets you chain multiple conditions, and only the first truthy branch executes — the rest are skipped.
  • Conditions are coerced to boolean via ToBoolean; only false, 0, "", null, undefined, and NaN are falsy.
  • Always use === instead of = in conditions, and always wrap blocks in { } to avoid subtle bugs.
  • The ternary operator (condition ? a : b) is a concise expression form of if...else, best reserved for simple value assignments.
  • Each block creates its own scope for let/const, and order matters when ranges in an else if chain can overlap.