if / else / else if

โ–ถ Try It Yourself

Control flow is how a program decides what to do next. Without it, code runs top-to-bottom with no decisions, no branches, no reactions to different situations. The if statement is the most fundamental control flow tool in JavaScript โ€” it evaluates a condition and runs a block of code only when that condition is true. In this lesson you will master if, else, else if, nested conditions, and the modern guard clause pattern that keeps code clean and readable.

if / else / else if Syntax

Form Syntax When It Runs
Single if if (condition) { ... } Only when condition is truthy
if / else if (c) { ... } else { ... } One branch always runs
if / else if / else if (c1) {} else if (c2) {} else {} First truthy branch runs; else is fallback
Nested if if (a) { if (b) { ... } } Both conditions must be true
Ternary condition ? valueA : valueB Inline expression โ€” not a statement

Condition Evaluation Rules

Condition Type Example Evaluates As
Strict equality age === 18 true only when age is exactly number 18
Comparison score >= 90 true when score is 90 or higher
Logical AND age >= 18 && hasId true only when both are true
Logical OR isAdmin || isMod true when either is true
Negation !isLoggedIn true when isLoggedIn is falsy
Nullish check user != null true when user is neither null nor undefined

Guard Clause vs Nested if

Style Approach Readability
Nested (avoid) Happy path wraps deeper and deeper inside ifs Pyramid of doom โ€” hard to follow
Guard clause (prefer) Return or throw early for invalid conditions; happy path at the bottom Flat, easy to read top-to-bottom
Note: Every value in a JavaScript condition is coerced to boolean. Only six values are falsy: false, 0, '', null, undefined, and NaN. Everything else โ€” including [], {}, and '0' โ€” is truthy. This means if (user) is a valid and common way to check whether a user object exists.
Tip: Prefer guard clauses over deep nesting. Instead of wrapping your entire function body in if (valid) { ... }, return early: if (!valid) return;. The rest of the function then only runs for valid input โ€” no indentation pyramid, no mental overhead tracking which closing brace belongs to which if.
Warning: Always use curly braces { } with if statements, even for single-line bodies. The braceless form if (x) doThing(); is legal but dangerous โ€” adding a second line to the “block” without braces will not be part of the condition, causing silent bugs that are very hard to spot.

Basic Example

// โ”€โ”€ Basic if / else if / else โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function getGrade(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(getGrade(95));  // 'A'
console.log(getGrade(82));  // 'B'
console.log(getGrade(55));  // 'F'

// โ”€โ”€ Logical AND / OR in conditions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function canAccessDashboard(user) {
    if (user !== null && user.isActive && (user.role === 'admin' || user.role === 'editor')) {
        return true;
    }
    return false;
}

console.log(canAccessDashboard({ isActive: true,  role: 'admin'  }));  // true
console.log(canAccessDashboard({ isActive: false, role: 'admin'  }));  // false
console.log(canAccessDashboard({ isActive: true,  role: 'viewer' }));  // false
console.log(canAccessDashboard(null));                                   // false

// โ”€โ”€ Guard clause pattern โ€” flat and readable โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function processOrder(order) {
    // Guard: reject invalid input early
    if (!order)               { return { error: 'No order provided' }; }
    if (!order.items?.length) { return { error: 'Order has no items' }; }
    if (order.total <= 0)     { return { error: 'Invalid order total' }; }
    if (!order.userId)        { return { error: 'No user ID on order' }; }

    // Happy path โ€” all guards passed, safe to proceed
    const tax      = order.total * 0.08;
    const shipping = order.total >= 35 ? 0 : 5.99;
    const grand    = order.total + tax + shipping;

    return {
        orderId:  `ORD-${Date.now()}`,
        subtotal: order.total,
        tax:      tax.toFixed(2),
        shipping: shipping.toFixed(2),
        total:    grand.toFixed(2),
    };
}

console.log(processOrder(null));
// { error: 'No order provided' }

console.log(processOrder({ items: ['widget'], total: 29.99, userId: 'u42' }));
// { orderId: 'ORD-...', subtotal: 29.99, tax: '2.40', shipping: '5.99', total: '38.38' }

// โ”€โ”€ Ternary for simple inline decisions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const cartCount   = 3;
const cartLabel   = cartCount === 1 ? '1 item' : `${cartCount} items`;
const freeShip    = cartCount >= 2 ? 'Free shipping!' : 'Add more for free shipping';

console.log(cartLabel);  // '3 items'
console.log(freeShip);   // 'Free shipping!'

// โ”€โ”€ Nullish-safe conditional โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function greetUser(user) {
    if (user?.name) {
        console.log(`Welcome back, ${user.name}!`);
    } else {
        console.log('Welcome, Guest!');
    }
}

greetUser({ name: 'Alice' });  // Welcome back, Alice!
greetUser(null);               // Welcome, Guest!
greetUser({});                 // Welcome, Guest!

How It Works

Step 1 โ€” JavaScript Evaluates the Condition to a Boolean

The expression inside if ( ... ) is coerced to true or false. Complex conditions using && and || short-circuit โ€” && stops at the first falsy value, || stops at the first truthy value. This means user !== null && user.isActive never accesses user.isActive when user is null.

Step 2 โ€” else if Chains Are Evaluated Top to Bottom

JavaScript tests each else if in order and runs the first branch whose condition is true, then skips all remaining branches. In getGrade, a score of 85 matches score >= 80 and returns 'B' immediately โ€” it never evaluates score >= 70.

Step 3 โ€” Guard Clauses Invert the Logic

Instead of if (valid) { doWork() }, guard clauses check for invalid conditions and return early. The function body after the guards only executes when all preconditions are met. This eliminates nesting and makes the success path obvious โ€” it is always the last block at the deepest indentation level.

Step 4 โ€” Optional Chaining Inside Conditions

user?.name returns undefined (falsy) if user is null or undefined, rather than throwing a TypeError. Combined with an if statement, this safely handles missing data without needing a separate null check first.

Step 5 โ€” Ternary Is an Expression, Not a Statement

Unlike if, the ternary operator produces a value โ€” it can be used directly in assignments, function arguments, and template literals. Keep ternaries simple: one condition, two short outcomes. For anything more complex, use a regular if/else block for readability.

Real-World Example: User Permission System

// permissions.js

const ROLES = {
    ADMIN:    'admin',
    EDITOR:   'editor',
    VIEWER:   'viewer',
    GUEST:    'guest',
};

function getUserPermissions(user) {
    // Guard clauses
    if (!user)             { return { canRead: false, canWrite: false, canDelete: false }; }
    if (!user.isActive)    { return { canRead: false, canWrite: false, canDelete: false }; }

    const role = user.role ?? ROLES.GUEST;

    if (role === ROLES.ADMIN) {
        return { canRead: true, canWrite: true, canDelete: true };
    } else if (role === ROLES.EDITOR) {
        return { canRead: true, canWrite: true, canDelete: false };
    } else if (role === ROLES.VIEWER) {
        return { canRead: true, canWrite: false, canDelete: false };
    } else {
        return { canRead: false, canWrite: false, canDelete: false };
    }
}

function renderUI(user) {
    const perms = getUserPermissions(user);

    const editBtn   = perms.canWrite  ? '<button>Edit</button>'   : '';
    const deleteBtn = perms.canDelete ? '<button>Delete</button>' : '';
    const content   = perms.canRead   ? '<article>Content...</article>' : '<p>Access denied</p>';

    return `${content}${editBtn}${deleteBtn}`;
}

// Tests
const admin  = { role: 'admin',  isActive: true  };
const editor = { role: 'editor', isActive: true  };
const banned = { role: 'admin',  isActive: false };

console.log(getUserPermissions(admin));   // { canRead: true, canWrite: true, canDelete: true }
console.log(getUserPermissions(editor));  // { canRead: true, canWrite: true, canDelete: false }
console.log(getUserPermissions(banned));  // { canRead: false, canWrite: false, canDelete: false }
console.log(getUserPermissions(null));    // { canRead: false, canWrite: false, canDelete: false }

Common Mistakes

Mistake 1 โ€” Assignment instead of comparison inside if

โŒ Wrong โ€” single = assigns and always evaluates truthy:

if (user = getUser()) { }   // assigns getUser() to user โ€” always true if getUser returns anything

โœ… Correct โ€” use === for comparison:

if (user === getUser()) { } // compares โ€” or simply: if (getUser()) { }

Mistake 2 โ€” Missing braces causing silent logic bugs

โŒ Wrong โ€” second line is NOT inside the if block:

if (isAdmin)
    showPanel();
    deleteButton.show();  // always runs โ€” NOT part of the if!

โœ… Correct โ€” always use braces:

if (isAdmin) {
    showPanel();
    deleteButton.show();  // now correctly inside the if block
}

Mistake 3 โ€” Deep nesting instead of guard clauses

โŒ Wrong โ€” pyramid of doom, hard to read:

function process(data) {
    if (data) {
        if (data.user) {
            if (data.user.isActive) {
                // actual work buried 3 levels deep
            }
        }
    }
}

โœ… Correct โ€” guard clauses, flat and clear:

function process(data) {
    if (!data)              return;
    if (!data.user)         return;
    if (!data.user.isActive) return;
    // actual work here โ€” no nesting
}

▶ Try It Yourself

Quick Reference

Pattern Syntax Best For
Simple branch if (x) { } else { } Two possible outcomes
Multiple branches if (a) {} else if (b) {} else {} 3+ mutually exclusive outcomes
Guard clause if (!valid) return; Early exit for invalid input
Ternary x ? a : b Simple inline value selection
Nullish-safe if (obj?.prop) { } Condition on possibly-null object
Combined condition if (a && b && c) { } All must be true

🧠 Test Yourself

What is the main advantage of using guard clauses over nested if statements?





โ–ถ Try It Yourself