Control Flow

40 min read Beginner

Learn to control the flow of your programs using conditionals and loops. Make decisions, repeat actions, and create dynamic, responsive code.

Conditional Statements

Conditional statements allow your code to make decisions. They execute different code blocks based on whether a condition is true or false.

if Statement

The if statement executes a block of code only if a condition is true.

script.js
const age = 18;

// Basic if statement
if (age >= 18) {
    console.log("You are an adult");
}

// The condition can be any expression that evaluates to truthy/falsy
const isLoggedIn = true;
if (isLoggedIn) {
    console.log("Welcome back!");
}

// Checking for existence
const username = "john_doe";
if (username) {
    console.log(`Hello, ${username}`);
}

if...else Statement

Use else to specify code that runs when the condition is false.

script.js
const temperature = 15;

if (temperature > 25) {
    console.log("It's hot outside!");
} else {
    console.log("It's not too hot.");
}

// Checking user authentication
const user = null;

if (user) {
    console.log(`Welcome, ${user.name}`);
} else {
    console.log("Please log in");
}

else if - Multiple Conditions

Chain multiple conditions using else if.

script.js
const score = 85;

if (score >= 90) {
    console.log("Grade: A");
} else if (score >= 80) {
    console.log("Grade: B");
} else if (score >= 70) {
    console.log("Grade: C");
} else if (score >= 60) {
    console.log("Grade: D");
} else {
    console.log("Grade: F");
}

// Time-based greeting
const hour = new Date().getHours();

if (hour < 12) {
    console.log("Good morning!");
} else if (hour < 17) {
    console.log("Good afternoon!");
} else if (hour < 21) {
    console.log("Good evening!");
} else {
    console.log("Good night!");
}

Order Matters

Conditions are checked in order, and only the first matching block runs. Put more specific conditions before general ones.

Ternary Operator

A shorthand for simple if-else statements. Syntax: condition ? valueIfTrue : valueIfFalse

script.js
const age = 20;

// Instead of:
// if (age >= 18) {
//     status = "adult";
// } else {
//     status = "minor";
// }

// Use ternary:
const status = age >= 18 ? "adult" : "minor";
console.log(status); // "adult"

// Inline in template literals
console.log(`You are ${age >= 18 ? "an adult" : "a minor"}`);

// Nested ternary (use sparingly, can be hard to read)
const score = 85;
const grade = score >= 90 ? "A" 
            : score >= 80 ? "B" 
            : score >= 70 ? "C" 
            : "F";

// Great for conditional values
const greeting = user ? `Hello, ${user.name}` : "Hello, guest";
const discount = isPremium ? 0.2 : 0;

Advanced Ternary Patterns

The ternary operator is powerful for inline conditionals, but use it wisely to maintain readability.

Ternary Patterns
// Pattern 1: Default values with ternary
const name = inputName ? inputName : "Anonymous";
// Better with nullish coalescing:
const name = inputName ?? "Anonymous";

// Pattern 2: Conditional function calls
const result = isAsync ? await fetchData() : getData();

// Pattern 3: Conditional object properties
const config = {
    mode: isDev ? "development" : "production",
    debug: isDev ? true : false,
    apiUrl: isDev 
        ? "http://localhost:3000" 
        : "https://api.example.com"
};

// Pattern 4: Conditional array methods
const items = shouldSort 
    ? data.sort((a, b) => a.name.localeCompare(b.name))
    : data;

// Pattern 5: Conditional class names (common in React)
const className = `button ${isActive ? "active" : ""} ${isDisabled ? "disabled" : ""}`.trim();

// Pattern 6: Conditional JSX/template rendering
const message = count === 0 
    ? "No items"
    : count === 1 
        ? "1 item" 
        : `${count} items`;

// ❌ Avoid: Over-nested ternaries (hard to read)
const result = a ? b ? c ? "abc" : "ab" : "a" : "none";

// ✅ Better: Use if-else or switch for complex logic
function getResult(a, b, c) {
    if (!a) return "none";
    if (!b) return "a";
    if (!c) return "ab";
    return "abc";
}

When to Use Ternary

Use ternary for simple, inline conditionals. If you're nesting more than 2 levels or the logic is complex, switch to if-else for better readability.

switch Statement

Use switch when comparing one value against many possible matches. Cleaner than many else-if chains.

script.js
const day = "Monday";

switch (day) {
    case "Monday":
        console.log("Start of the work week");
        break;
    case "Tuesday":
    case "Wednesday":
    case "Thursday":
        console.log("Midweek");
        break;
    case "Friday":
        console.log("TGIF!");
        break;
    case "Saturday":
    case "Sunday":
        console.log("Weekend!");
        break;
    default:
        console.log("Invalid day");
}

// Switch with expressions
const fruit = "apple";
let color;

switch (fruit) {
    case "banana":
        color = "yellow";
        break;
    case "apple":
        color = "red";
        break;
    case "grape":
        color = "purple";
        break;
    default:
        color = "unknown";
}

console.log(`A ${fruit} is ${color}`); // "A apple is red"

Don't Forget break!

Without break, execution "falls through" to the next case. This is sometimes useful, but usually a bug if unintentional.

switch vs if-else: When to Use Which

Both constructs can handle multiple conditions, but they have different strengths:

Comparison
// ✅ Use switch when:
// - Comparing ONE variable against MULTIPLE exact values
// - Cases are discrete values (not ranges)
// - You need fall-through behavior

switch (action) {
    case "create":
    case "add":  // Fall-through for similar actions
        return handleCreate();
    case "update":
    case "edit":
        return handleUpdate();
    case "delete":
    case "remove":
        return handleDelete();
    default:
        return handleUnknown();
}

// ✅ Use if-else when:
// - Comparing ranges or complex conditions
// - Conditions involve multiple variables
// - Using operators like >, <, >=, <=

if (age < 13) {
    return "child";
} else if (age < 20) {
    return "teenager";  
} else if (age < 65) {
    return "adult";
} else {
    return "senior";
}

// ❌ switch can't easily do range checks:
// switch (age) { case < 13: ... }  // INVALID!

// ✅ Modern alternative: Object lookup (often fastest)
const actions = {
    create: handleCreate,
    add: handleCreate,
    update: handleUpdate,
    edit: handleUpdate,
    delete: handleDelete,
    remove: handleDelete
};

const handler = actions[action] || handleUnknown;
handler();

Performance Note

For most cases, performance difference is negligible. Object lookups are O(1) and often fastest for string/number keys. Choose based on readability first, then optimize if profiling shows a bottleneck.

Loops

Loops repeat code multiple times. They're essential for processing lists, waiting for conditions, and automating repetitive tasks.

for Loop

The classic loop with initialization, condition, and increment in one line.

script.js
// Basic for loop
// for (initialization; condition; increment) { code }
for (let i = 0; i < 5; i++) {
    console.log(i); // 0, 1, 2, 3, 4
}

// Counting backwards
for (let i = 5; i > 0; i--) {
    console.log(i); // 5, 4, 3, 2, 1
}

// Stepping by 2
for (let i = 0; i <= 10; i += 2) {
    console.log(i); // 0, 2, 4, 6, 8, 10
}

// Looping through an array
const fruits = ["apple", "banana", "cherry"];
for (let i = 0; i < fruits.length; i++) {
    console.log(`${i}: ${fruits[i]}`);
}
// Output:
// 0: apple
// 1: banana
// 2: cherry

// Nested loops (for grids, matrices)
for (let row = 1; row <= 3; row++) {
    for (let col = 1; col <= 3; col++) {
        console.log(`Row ${row}, Col ${col}`);
    }
}

while Loop

Repeats as long as a condition is true. Best when you don't know how many iterations you need.

script.js
// Basic while loop
let count = 0;
while (count < 5) {
    console.log(count);
    count++;
}
// Output: 0, 1, 2, 3, 4

// Waiting for a condition
let number = 1;
while (number <= 1000) {
    number *= 2;
}
console.log(number); // 1024 (first power of 2 over 1000)

// Processing until empty
const tasks = ["task1", "task2", "task3"];
while (tasks.length > 0) {
    const task = tasks.pop();
    console.log(`Processing: ${task}`);
}
// Output:
// Processing: task3
// Processing: task2
// Processing: task1

Beware Infinite Loops!

Always ensure the condition will eventually become false. An infinite loop will freeze your browser or crash your program.

do...while Loop

Like while, but the code runs at least once before checking the condition.

script.js
// Runs at least once
let num = 10;
do {
    console.log(num); // 10 (runs once even though condition is false)
    num++;
} while (num < 5);

// Useful for input validation (conceptually)
let userInput;
do {
    // In real code, this would prompt the user
    userInput = Math.random() > 0.3 ? "valid" : "invalid";
    console.log(`Got: ${userInput}`);
} while (userInput !== "valid");

for...of Loop (ES6)

The modern way to iterate over arrays and other iterable objects. Cleaner than traditional for loops.

script.js
// Iterate over array values
const colors = ["red", "green", "blue"];
for (const color of colors) {
    console.log(color);
}
// Output: red, green, blue

// Iterate over string characters
const word = "Hello";
for (const char of word) {
    console.log(char);
}
// Output: H, e, l, l, o

// With array entries (get index too)
for (const [index, color] of colors.entries()) {
    console.log(`${index}: ${color}`);
}
// Output:
// 0: red
// 1: green
// 2: blue

for...in Loop

Iterates over object property names (keys). Not recommended for arrays.

script.js
// Iterate over object keys
const user = {
    name: "John",
    age: 30,
    city: "New York"
};

for (const key in user) {
    console.log(`${key}: ${user[key]}`);
}
// Output:
// name: John
// age: 30
// city: New York

// Better alternatives for objects:
Object.keys(user).forEach(key => console.log(key));
Object.values(user).forEach(value => console.log(value));
Object.entries(user).forEach(([key, value]) => {
    console.log(`${key}: ${value}`);
});

break and continue

script.js
// break - exit the loop entirely
for (let i = 0; i < 10; i++) {
    if (i === 5) {
        break; // Stop when i reaches 5
    }
    console.log(i);
}
// Output: 0, 1, 2, 3, 4

// continue - skip to next iteration
for (let i = 0; i < 5; i++) {
    if (i === 2) {
        continue; // Skip when i is 2
    }
    console.log(i);
}
// Output: 0, 1, 3, 4

// Finding first match
const numbers = [3, 7, 2, 9, 5];
let firstEven;
for (const num of numbers) {
    if (num % 2 === 0) {
        firstEven = num;
        break;
    }
}
console.log(firstEven); // 2

// Skipping invalid items
const data = [1, null, 3, undefined, 5];
for (const item of data) {
    if (item == null) continue;
    console.log(item * 2);
}
// Output: 2, 6, 10

Labeled Statements (Nested Loop Control)

script.js
// Labels let you break/continue outer loops
outerLoop: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) {
            break outerLoop; // Breaks BOTH loops
        }
        console.log(`i=${i}, j=${j}`);
    }
}
// Output:
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
// (stops at i=1, j=1)

// Without label, only inner loop breaks
for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (j === 1) {
            break; // Only breaks inner loop
        }
        console.log(`i=${i}, j=${j}`);
    }
}

Loop Optimization Tips

For most code, loop performance isn't a concern. But when processing large datasets, these optimizations can make a significant difference:

Cache Array Length

Length Caching
const largeArray = new Array(1000000).fill(0);

// ❌ Recalculates length each iteration
for (let i = 0; i < largeArray.length; i++) {
    // process
}

// ✅ Cache length (minor but measurable improvement)
for (let i = 0, len = largeArray.length; i < len; i++) {
    // process
}

// ✅ Or use for...of (handles it internally)
for (const item of largeArray) {
    // process
}

Avoid Work Inside Loops

Loop Work Optimization
// ❌ Repeatedly accessing DOM
for (let i = 0; i < items.length; i++) {
    document.getElementById("container").appendChild(
        createItem(items[i])
    );
}

// ✅ Cache DOM reference outside loop
const container = document.getElementById("container");
for (let i = 0; i < items.length; i++) {
    container.appendChild(createItem(items[i]));
}

// ✅ Even better: Build fragment, insert once
const fragment = document.createDocumentFragment();
for (const item of items) {
    fragment.appendChild(createItem(item));
}
container.appendChild(fragment);

// ❌ Unnecessary calculations inside loop
for (let i = 0; i < arr.length; i++) {
    const factor = Math.sqrt(baseValue) * coefficient;
    result[i] = arr[i] * factor;
}

// ✅ Calculate constants outside
const factor = Math.sqrt(baseValue) * coefficient;
for (let i = 0; i < arr.length; i++) {
    result[i] = arr[i] * factor;
}

Choose the Right Loop

Loop Selection
const arr = [1, 2, 3, 4, 5];

// Performance ranking (generally):
// 1. Traditional for loop - fastest
// 2. for...of - very close, more readable
// 3. forEach - slightly slower, functional style
// 4. for...in - slowest, not for arrays!

// ✅ Use traditional for when you need index manipulation
for (let i = arr.length - 1; i >= 0; i--) {
    // Reverse iteration
}

// ✅ Use for...of for clean iteration
for (const item of arr) {
    // Most readable for simple iteration
}

// ✅ Use forEach for side effects
arr.forEach((item, index) => {
    console.log(`${index}: ${item}`);
});

// ✅ Use map/filter/reduce for transformations
const doubled = arr.map(x => x * 2);
const evens = arr.filter(x => x % 2 === 0);
const sum = arr.reduce((acc, x) => acc + x, 0);

// ❌ Don't use for...in with arrays
for (const i in arr) {
    // i is a string, not a number!
    // Also iterates inherited properties
}

Early Exit Strategies

Early Exit
// ✅ Use break to exit early when found
function findUser(users, id) {
    for (const user of users) {
        if (user.id === id) {
            return user; // Exit immediately
        }
    }
    return null;
}

// ✅ Use array methods with early exit
const found = users.find(user => user.id === id);
const exists = users.some(user => user.id === id);

// ✅ For validation, exit on first failure
function validateAll(items) {
    for (const item of items) {
        if (!isValid(item)) {
            return false; // Don't check rest
        }
    }
    return true;
}

// Same with every()
const allValid = items.every(isValid);

The Golden Rule

Write readable code first. Only optimize loops when profiling shows they're a bottleneck. Premature optimization is the root of all evil! 99% of the time, for...of or array methods are the right choice.

Summary

You've learned how to control the flow of your JavaScript programs:

Next Steps

With conditionals and loops, you can create dynamic programs. Next, learn about functions to organize your code into reusable building blocks!