JavaScript Control Flow

Control flow determines which code runs and when. Learn if/else, switch, ternary, short-circuit evaluation, and the guard clause pattern that keeps functions clean and readable.

Beginner 9 min read 10 examples

if / else

The if statement runs a block if its condition is truthy. The optional else block runs when the condition is falsy.

JavaScript
const age = 20;

if (age >= 18) {
    console.log("Adult");
} else {
    console.log("Minor");
}
// "Adult"

// Falsy values that trigger else
if (0)         { } // does not run (0 is falsy)
if ("")        { } // does not run
if (null)      { } // does not run
if (undefined) { } // does not run
if (NaN)       { } // does not run

// Truthy values that trigger if
if (1)         { } // runs (any nonzero number)
if ("0")       { } // runs (non-empty string, even "false")
if ([])        { } // runs (empty array is truthy)
if ({})        { } // runs (empty object is truthy)

// Braces are optional for single statements - but always use them
if (age >= 18) console.log("ok"); // works but avoid this style

else if Chains

Chain multiple conditions with else if. Only the first matching block executes; the rest are skipped.

JavaScript
function classify(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(classify(95)); // "A"
console.log(classify(75)); // "C"
console.log(classify(55)); // "F"

// Alternative: lookup object (cleaner for fixed mappings)
function getDayName(num) {
    const days = {
        1: "Monday",  2: "Tuesday",  3: "Wednesday",
        4: "Thursday",5: "Friday",   6: "Saturday",  7: "Sunday"
    };
    return days[num] ?? "Invalid day";
}
console.log(getDayName(3)); // "Wednesday"

switch / case

Use switch when comparing one value against multiple fixed options. Each case needs a break to prevent falling through to the next.

JavaScript
const command = "start";

switch (command) {
    case "start":
        console.log("Starting...");
        break;
    case "stop":
        console.log("Stopping...");
        break;
    case "pause":
        console.log("Pausing...");
        break;
    default:
        console.log(`Unknown command: ${command}`);
}
// "Starting..."

// Fallthrough: multiple cases share one block
const day = "Saturday";
switch (day) {
    case "Saturday":
    case "Sunday":
        console.log("Weekend!");
        break;
    default:
        console.log("Weekday");
}
// "Weekend!"

// Switch inside a function - use return instead of break
function getStatus(code) {
    switch (code) {
        case 200: return "OK";
        case 404: return "Not Found";
        case 500: return "Server Error";
        default:  return "Unknown";
    }
}
console.log(getStatus(404)); // "Not Found"
Always Use break in switch Cases

Without break, JavaScript falls through to the next case and executes it too. This is rarely what you want and is a common source of bugs. Inside functions, prefer return over break - it eliminates the fall-through risk entirely and makes the intent clearer.

Ternary Operator

The ternary operator is a compact if/else for expressions. It returns a value and is ideal for simple conditional assignments.

JavaScript
const age = 20;

// Ternary: condition ? valueIfTrue : valueIfFalse
const label = age >= 18 ? "Adult" : "Minor";
console.log(label); // "Adult"

// In template literals
const name = "Alice";
console.log(`Welcome, ${name ? name : "Guest"}!`);

// Nested ternary - keep shallow for readability
const score = 75;
const grade = score >= 90 ? "A"
            : score >= 70 ? "B"
            : score >= 50 ? "C"
            : "F";

// Prefer if/else for complex conditions or multiple statements
// Ternary is for single-expression decisions only

Short-Circuit Evaluation

Logical operators && and || stop evaluating as soon as the result is determined. This enables compact conditional logic.

JavaScript
// && - evaluates right side only if left is truthy
const user = { name: "Alice", isAdmin: true };

user.isAdmin && console.log("Show admin panel"); // "Show admin panel"

const guest = null;
guest && console.log(guest.name); // safe - right side never runs

// || - evaluates right side only if left is falsy
const port = process.env.PORT || 3000;     // default value pattern
const displayName = user.name || "Anonymous";

// ?? - right side only for null/undefined (not 0 or "")
const timeout = config.timeout ?? 5000;
const retries = config.retries ?? 3;

// Combining for conditional rendering (React-style pattern)
const isLoggedIn = true;
const element = isLoggedIn && `
Welcome, ${user.name}
`; // Warning: 0 && ... evaluates to 0 (renders "0" in React) const count = 0; const show = count && "Items"; // 0 - not false, renders as 0! const showFix = count > 0 && "Items"; // false - correct

Guard Clauses (Early Return)

Guard clauses return early from a function when conditions are not met. They flatten nested if/else into a linear, readable structure.

JavaScript
// WITHOUT guard clauses - deeply nested
function processOrder(user, cart, payment) {
    if (user) {
        if (cart.items.length > 0) {
            if (payment.isValid) {
                // actual logic buried here
                return placeOrder(user, cart, payment);
            } else {
                return { error: "Invalid payment" };
            }
        } else {
            return { error: "Cart is empty" };
        }
    } else {
        return { error: "Not logged in" };
    }
}

// WITH guard clauses - flat and easy to read
function processOrderClean(user, cart, payment) {
    if (!user)                   return { error: "Not logged in" };
    if (cart.items.length === 0) return { error: "Cart is empty" };
    if (!payment.isValid)        return { error: "Invalid payment" };

    // happy path - no nesting
    return placeOrder(user, cart, payment);
}

// Guard clauses for input validation
function divide(a, b) {
    if (typeof a !== "number" || typeof b !== "number") {
        throw new TypeError("Arguments must be numbers");
    }
    if (b === 0) throw new Error("Division by zero");
    return a / b;
}

Nullish and Optional Chaining in Conditions

JavaScript
const user = {
    name: "Alice",
    profile: null
};

// Checking nested properties safely
if (user?.profile?.bio) {
    console.log(user.profile.bio);
} else {
    console.log("No bio set");
}
// "No bio set"

// Checking with nullish coalescing for defaults
const theme  = user?.settings?.theme ?? "light";
const lang   = user?.settings?.language ?? "en";

// Combining conditions
function canEdit(user, doc) {
    return user?.isAdmin || doc?.ownerId === user?.id;
}

// Checking array safely
const items = user?.cart?.items;
if (items?.length > 0) {
    console.log(`Cart has ${items.length} items`);
}

Frequently Asked Questions

Use switch when comparing one variable against many fixed values - it is cleaner and often faster than a long else if chain. Use if/else when conditions involve ranges, complex expressions, or multiple variables. Both can always replace the other, but readability determines which to pick. Always add break to each switch case, or use return when inside a function.

A guard clause is an early return at the start of a function that handles invalid or edge-case input immediately, before the main logic runs. It "guards" the rest of the function. It is preferred over deeply nested if/else because it keeps the happy path at the lowest level of indentation and makes code easier to read linearly.

There are exactly 8 falsy values: false, 0, -0, 0n (BigInt zero), "" (empty string), null, undefined, and NaN. Everything else is truthy, including "0" (non-empty string), [] (empty array), and {} (empty object). This matters for if conditions and short-circuit operators.

Yes. JavaScript's switch uses strict equality (===) for comparison, so it works with strings, numbers, or any primitive. Example: switch (command) { case "start": ...; break; case "stop": ...; break; }. You cannot use ranges or complex conditions in switch cases - use if/else if for those.