JavaScript Control Flow and Loops

1. Conditional Statements (if, else, switch, ternary)

Statement Syntax Use Case Notes
if if (condition) { } Execute block if condition is truthy Most common conditional
if...else if (cond) { } else { } Execute one of two blocks Binary choice
if...else if...else if (c1) { } else if (c2) { } else { } Multiple conditions, evaluated in order First truthy wins
switch switch(expr) { case val: break; } Multiple discrete values, strict equality (===) Don't forget break
Ternary condition ? trueVal : falseVal Inline conditional expression Returns value, concise
Nested Ternary c1 ? v1 : c2 ? v2 : v3 Multiple conditions inline Can be hard to read
Feature if/else switch Ternary
Returns Value No (statements) No (statements) Yes (expression)
Multiple Conditions ✓ Any expression Single expression, multiple cases One condition per ternary
Comparison Type Any truthy/falsy Strict equality (===) Any truthy/falsy
Fall-through N/A ✓ Without break N/A
Best For Complex conditions, ranges Many discrete values Simple inline conditions

Example: Conditional statements

// if statement
let age = 20;
if (age >= 18) {
    console.log("Adult");
}

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

// if...else if...else
let score = 85;
if (score >= 90) {
    console.log("A");
} else if (score >= 80) {
    console.log("B");
} else if (score >= 70) {
    console.log("C");
} else {
    console.log("F");
}

// switch statement
let day = "Monday";
switch (day) {
    case "Monday":
    case "Tuesday":
    case "Wednesday":
    case "Thursday":
    case "Friday":
        console.log("Weekday");
        break;  // Important!
    case "Saturday":
    case "Sunday":
        console.log("Weekend");
        break;
    default:
        console.log("Invalid day");
}

// switch with expressions (modern pattern)
let result = switch(status) {  // Not yet in JS, but coming
    case 200: "OK";
    case 404: "Not Found";
    default: "Unknown";
};

// Ternary operator
let message = age >= 18 ? "Adult" : "Minor";
console.log(message);

// Nested ternary
let grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F";

// Ternary with side effects (avoid for readability)
let value = condition ? (count++, doSomething()) : doSomethingElse();

// Multiple ternaries (can be hard to read)
let type = age < 13 ? "child" 
         : age < 20 ? "teen"
         : age < 65 ? "adult"
         : "senior";

// Guard clauses (early return pattern)
function processUser(user) {
    if (!user) return;              // Guard clause
    if (!user.isActive) return;     // Guard clause
    if (!user.hasPermission) return; // Guard clause
    
    // Main logic here
    console.log("Processing user...");
}
Warning: Always use break in switch cases to prevent fall-through (unless intentional). Nested ternaries can reduce readability; use if/else for complex logic.

2. Loop Statements (for, while, do-while)

Loop Type Syntax When to Use Characteristics
for for (init; cond; update) { } Known iteration count Initialization, condition, update in header
while while (condition) { } Unknown iteration count, condition first Tests condition before each iteration
do-while do { } while (condition); At least one iteration guaranteed Tests condition after each iteration
Feature for while do-while
Minimum Iterations 0 (can skip entirely) 0 (can skip entirely) 1 (always executes once)
Condition Check Before each iteration Before each iteration After each iteration
Loop Variable Scope Block-scoped if using let/const Declared outside loop Declared outside loop
Best For Counter-based iteration Condition-based iteration Execute-then-check pattern

Example: Classic loop statements

// for loop - most common for arrays/counters
for (let i = 0; i < 5; i++) {
    console.log(i);  // 0, 1, 2, 3, 4
}

// Multiple variables in for loop
for (let i = 0, j = 10; i < 5; i++, j--) {
    console.log(i, j);  // (0,10), (1,9), (2,8), (3,7), (4,6)
}

// Infinite loop (condition always true)
// for (;;) {
//     console.log("Forever");
//     break; // Need break to exit
// }

// while loop - condition-based
let count = 0;
while (count < 5) {
    console.log(count);
    count++;
}

// while with complex condition
let items = [1, 2, 3, 4, 5];
let i = 0;
while (i < items.length && items[i] !== 3) {
    console.log(items[i]);
    i++;
}

// Reading input until valid
let input;
while (!input || input.trim() === "") {
    // input = prompt("Enter value:");  // Browser example
    input = "test";  // Simulated
}

// do-while - at least one iteration
let num = 0;
do {
    console.log(num);  // Executes once even though num < 1 is false
    num++;
} while (num < 1);

// Menu loop pattern
let choice;
do {
    // Display menu
    console.log("1. Option 1");
    console.log("2. Option 2");
    console.log("3. Exit");
    // choice = getUserInput();
    choice = 3;  // Simulated
} while (choice !== 3);

// Empty for loop (all work in condition)
let arr = [1, 2, 3];
for (let i = 0; i < arr.length && arr[i]++ < 10;);
console.log(arr);  // [2, 3, 4]

// Nested loops
for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        console.log(`${i}, ${j}`);
    }
}

// Loop with multiple updates
for (let i = 0; i < 10; i += 2) {
    console.log(i);  // 0, 2, 4, 6, 8
}

// Reverse loop
for (let i = 5; i >= 0; i--) {
    console.log(i);  // 5, 4, 3, 2, 1, 0
}
Note: Use for for counter-based iteration, while for condition-based, and do-while when you need at least one iteration before checking the condition.

3. Enhanced Loops (for...in, for...of, for await...of)

Loop Type Syntax Iterates Over Returns Use For
for...in for (key in obj) { } Enumerable properties Property names (strings) Object properties
for...of ES6 for (val of iterable) { } Iterable objects Values Arrays, strings, Maps, Sets
for await...of ES2018 for await (val of asyncIterable) { } Async iterables Resolved promises Async generators, streams
Feature for...in for...of
Works with Objects ✓ Yes (enumerable properties) ✗ No (unless iterable)
Works with Arrays ✓ Yes (indices as strings) ✓ Yes (values directly)
Works with Strings ✓ Yes (indices) ✓ Yes (characters)
Works with Map/Set ✗ No ✓ Yes
Includes Prototype Chain ✓ Yes (inherited properties) ✗ No
Recommended For Object properties (use with hasOwnProperty) Iterables (arrays, strings, collections)

Example: Enhanced loops

// for...in - iterates over object properties
const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
    console.log(key, obj[key]);  // "a" 1, "b" 2, "c" 3
}

// for...in with arrays (NOT recommended - use for...of)
const arr = [10, 20, 30];
for (let index in arr) {
    console.log(index, arr[index]);  // "0" 10, "1" 20, "2" 30
    console.log(typeof index);       // "string" (not number!)
}

// for...in includes inherited properties
Object.prototype.inheritedProp = "inherited";
const obj2 = { own: "value" };
for (let key in obj2) {
    console.log(key);  // "own", "inheritedProp"
}

// Use hasOwnProperty to filter
for (let key in obj2) {
    if (obj2.hasOwnProperty(key)) {
        console.log(key);  // "own" only
    }
}

// for...of - iterates over iterable values (ES6)
const arr2 = [10, 20, 30];
for (let value of arr2) {
    console.log(value);  // 10, 20, 30 (values, not indices)
}

// for...of with strings
const str = "hello";
for (let char of str) {
    console.log(char);  // "h", "e", "l", "l", "o"
}

// for...of with Map
const map = new Map([['a', 1], ['b', 2]]);
for (let [key, value] of map) {
    console.log(key, value);  // "a" 1, "b" 2
}

// for...of with Set
const set = new Set([1, 2, 3]);
for (let value of set) {
    console.log(value);  // 1, 2, 3
}

// for...of with array methods
const arr3 = [1, 2, 3, 4, 5];
for (let value of arr3.entries()) {
    console.log(value);  // [0, 1], [1, 2], [2, 3]...
}

for (let [index, value] of arr3.entries()) {
    console.log(index, value);  // 0 1, 1 2, 2 3...
}

// for await...of - async iteration (ES2018)
async function fetchSequentially() {
    const urls = ['url1', 'url2', 'url3'];
    
    // Create async iterable
    async function* fetchUrls() {
        for (let url of urls) {
            yield fetch(url);  // Simulated
        }
    }
    
    // Iterate and wait for each promise
    for await (let response of fetchUrls()) {
        console.log(await response.text());
    }
}

// for await...of with Promise array
async function processPromises() {
    const promises = [
        Promise.resolve(1),
        Promise.resolve(2),
        Promise.resolve(3)
    ];
    
    for await (let value of promises) {
        console.log(value);  // 1, 2, 3 (waits for each)
    }
}

// Custom iterable with for...of
const customIterable = {
    data: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => ({
                value: this.data[index],
                done: index++ >= this.data.length
            })
        };
    }
};

for (let value of customIterable) {
    console.log(value);  // 1, 2, 3
}
Warning: Use for...of for arrays (not for...in). With for...in on objects, always use hasOwnProperty() to avoid inherited properties.

4. Jump Statements (break, continue, return)

Statement Syntax Effect Use In
break break; Exits loop or switch immediately Loops, switch
break label break labelName; Exits named loop/block Nested loops
continue continue; Skips to next iteration Loops only
continue label continue labelName; Continues named loop Nested loops
return return value; Exits function with value Functions only
return (void) return; Exits function, returns undefined Functions only
Statement break continue return
In for loop ✓ Exits loop ✓ Next iteration ✓ Exits function (and loop)
In while loop ✓ Exits loop ✓ Next iteration ✓ Exits function (and loop)
In switch ✓ Exits switch ✗ Syntax error ✓ Exits function (and switch)
In function ✗ Must be in loop/switch ✗ Must be in loop ✓ Exits function
With labels ✓ Can target outer loop ✓ Can target outer loop ✗ Cannot use labels

Example: Jump statements

// break - exit loop immediately
for (let i = 0; i < 10; i++) {
    if (i === 5) break;  // Exit loop when i is 5
    console.log(i);      // 0, 1, 2, 3, 4
}

// break in nested loop (only exits inner loop)
for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (j === 1) break;  // Exits inner loop only
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  1,0  2,0

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

// continue with condition
const arr = [1, 2, 3, 4, 5];
for (let num of arr) {
    if (num % 2 === 0) continue;  // Skip even numbers
    console.log(num);             // 1, 3, 5
}

// return - exit function
function findValue(arr, target) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] === target) {
            return i;  // Exit function, return index
        }
    }
    return -1;  // Not found
}

console.log(findValue([1, 2, 3], 2));  // 1
console.log(findValue([1, 2, 3], 5));  // -1

// return without value (returns undefined)
function logMessage(msg) {
    if (!msg) return;  // Early exit
    console.log(msg);
}

// Multiple return points (guard clauses)
function validateUser(user) {
    if (!user) return { valid: false, error: "No user" };
    if (!user.name) return { valid: false, error: "No name" };
    if (!user.email) return { valid: false, error: "No email" };
    return { valid: true };
}

// break in switch
function getDayType(day) {
    switch (day) {
        case 'Mon':
        case 'Tue':
        case 'Wed':
        case 'Thu':
        case 'Fri':
            return 'Weekday';
            // No break needed after return
        case 'Sat':
        case 'Sun':
            return 'Weekend';
        default:
            return 'Invalid';
    }
}

// Finding first match
const users = [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 30 },
    { name: 'Charlie', age: 35 }
];

let found;
for (let user of users) {
    if (user.age > 28) {
        found = user;
        break;  // Stop after finding first match
    }
}
console.log(found);  // Bob

// Skip invalid items
const data = [1, null, 2, undefined, 3, NaN, 4];
for (let item of data) {
    if (item == null || Number.isNaN(item)) continue;
    console.log(item);  // 1, 2, 3, 4
}
Note: Use break to exit loops early, continue to skip iterations, and return to exit functions. For nested loops, consider labeled statements for more control.

5. Exception Handling (try, catch, finally, throw)

Keyword Purpose Required Description
try Execute code that may throw ✓ Yes Contains code to be tested for errors
catch Handle exceptions ✓ Yes (or finally) Executes if error thrown in try block
finally Cleanup code ✗ Optional Always executes (even if error/return)
throw Throw exception N/A Creates and throws error object
Error Type When Thrown Example
Error Generic error (base class) new Error("message")
SyntaxError Invalid JavaScript syntax eval("{")
ReferenceError Invalid reference/undeclared variable console.log(undeclaredVar)
TypeError Value not of expected type null.property
RangeError Value not in allowed range new Array(-1)
URIError Invalid URI encoding/decoding decodeURI("%")

Example: Exception handling

// Basic try-catch
try {
    let result = riskyOperation();
    console.log(result);
} catch (error) {
    console.error("Error:", error.message);
}

// Accessing error properties
try {
    throw new Error("Something went wrong");
} catch (error) {
    console.log(error.name);     // "Error"
    console.log(error.message);  // "Something went wrong"
    console.log(error.stack);    // Stack trace
}

// try-catch-finally
try {
    console.log("Try block");
    // throw new Error("Test");
} catch (error) {
    console.log("Catch block:", error.message);
} finally {
    console.log("Finally always runs");
}

// finally runs even with return
function testFinally() {
    try {
        return "from try";
    } catch (error) {
        return "from catch";
    } finally {
        console.log("Finally executed");  // Runs before return
    }
}
console.log(testFinally());  // "Finally executed", then "from try"

// Throwing errors
function divide(a, b) {
    if (b === 0) {
        throw new Error("Division by zero");
    }
    return a / b;
}

try {
    console.log(divide(10, 0));
} catch (error) {
    console.log("Caught:", error.message);
}

// Throwing custom errors
class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}

function validateAge(age) {
    if (age < 0) {
        throw new ValidationError("Age cannot be negative");
    }
    if (age > 150) {
        throw new ValidationError("Age too high");
    }
    return true;
}

try {
    validateAge(-5);
} catch (error) {
    if (error instanceof ValidationError) {
        console.log("Validation error:", error.message);
    } else {
        console.log("Other error:", error);
    }
}

// Nested try-catch
try {
    try {
        throw new Error("Inner error");
    } catch (innerError) {
        console.log("Inner catch:", innerError.message);
        throw new Error("Outer error");  // Re-throw or new error
    }
} catch (outerError) {
    console.log("Outer catch:", outerError.message);
}

// Catching specific error types
try {
    // Some operation
    JSON.parse("invalid json");
} catch (error) {
    if (error instanceof SyntaxError) {
        console.log("JSON syntax error");
    } else if (error instanceof TypeError) {
        console.log("Type error");
    } else {
        console.log("Unknown error");
    }
}

// Async error handling
async function fetchData() {
    try {
        const response = await fetch('/api/data');
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Fetch error:", error);
        throw error;  // Re-throw for caller to handle
    } finally {
        console.log("Cleanup operations");
    }
}

// Error without catch (just finally)
try {
    console.log("Executing...");
} finally {
    console.log("Cleanup");  // Valid: try-finally without catch
}

// Throwing non-Error objects (possible but not recommended)
try {
    throw "String error";  // Works but not best practice
    throw { code: 500 };   // Works but no stack trace
    throw 42;              // Works but no context
} catch (error) {
    console.log(typeof error);  // Could be anything
}
Warning: Always catch errors for operations that may fail (network, parsing, etc.). Use finally for cleanup that must run regardless of success/failure.

6. Labels and Labeled Statements

Feature Syntax Use With Purpose
Label Definition labelName: statement Any statement/block Names a statement for reference
Labeled break break labelName; Loops, blocks Exits named loop/block
Labeled continue continue labelName; Loops only Continues named loop
Use Case Without Labels With Labels
Break from nested loop Only breaks inner loop Can break outer loop directly
Continue outer loop Requires flag variable Direct continue to outer loop
Exit multiple levels Multiple break statements Single labeled break
Code clarity Complex control flow Explicit jump targets

Example: Labeled statements

// Labeled break - exit outer loop
outer: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) {
            break outer;  // Breaks out of outer loop
        }
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  0,1  0,2  1,0

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

// Labeled continue - continue outer loop
outer: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (j === 1) {
            continue outer;  // Continues outer loop, skips rest of inner
        }
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  1,0  2,0

// Finding element in 2D array
const matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

let target = 5;
let found = false;

search: for (let i = 0; i < matrix.length; i++) {
    for (let j = 0; j < matrix[i].length; j++) {
        if (matrix[i][j] === target) {
            console.log(`Found at [${i}][${j}]`);
            found = true;
            break search;  // Exit both loops
        }
    }
}

// Multiple nested loops with labels
outer: for (let i = 0; i < 2; i++) {
    middle: for (let j = 0; j < 2; j++) {
        inner: for (let k = 0; k < 2; k++) {
            if (i === 1) break outer;      // Exit all loops
            if (j === 1) break middle;     // Exit middle and inner
            if (k === 1) break inner;      // Exit only inner
            console.log(`${i},${j},${k}`);
        }
    }
}

// Labeled block (not loop)
blockLabel: {
    console.log("Start");
    if (someCondition) {
        break blockLabel;  // Exit block early
    }
    console.log("This may not execute");
}
console.log("After block");

// Complex search with labeled break
const data = [
    { id: 1, items: [10, 20, 30] },
    { id: 2, items: [40, 50, 60] },
    { id: 3, items: [70, 80, 90] }
];

let searchValue = 50;
let result = null;

dataSearch: for (let group of data) {
    for (let item of group.items) {
        if (item === searchValue) {
            result = { groupId: group.id, value: item };
            break dataSearch;  // Found, exit all loops
        }
    }
}

console.log(result);  // { groupId: 2, value: 50 }

// Alternative to labeled break (using function return)
function findInMatrix(matrix, target) {
    for (let i = 0; i < matrix.length; i++) {
        for (let j = 0; j < matrix[i].length; j++) {
            if (matrix[i][j] === target) {
                return [i, j];  // Return exits function (simpler)
            }
        }
    }
    return null;
}

// Labeled continue with condition
outer: for (let i = 0; i < 5; i++) {
    for (let j = 0; j < 5; j++) {
        if (i + j > 5) {
            continue outer;  // Skip to next outer iteration
        }
        console.log(`Sum: ${i + j}`);
    }
}
Note: Labeled statements are useful for complex nested loops but can reduce readability. Consider refactoring into separate functions with return as a cleaner alternative.

Section 5 Summary

  • Conditionals: Use if/else for complex logic, switch for discrete values, ternary for inline expressions
  • Loops: for (known count), while (condition-based), do-while (at least once)
  • Enhanced loops: for...of for iterables (arrays, strings), for...in for object properties, for await...of for async
  • Jump statements: break exits loops/switch, continue skips to next iteration, return exits functions
  • Exception handling: try-catch-finally for error handling; throw to raise errors; finally always executes
  • Labels: Enable break/continue to target outer loops; useful for nested structures but consider function refactoring