Part 4: Control Flow
Directing Program Execution
Conditional Statements
Conditional statements allow your program to make decisions based on conditions.
The if Statement
javascriptconst age = 18; if (age >= 18) { console.log('You are an adult'); } // Single statement (braces optional but recommended) if (age >= 18) console.log('Adult'); // Multiple conditions const temperature = 25; if (temperature > 30) { console.log('Hot day!'); console.log('Stay hydrated'); }
The if...else Statement
javascriptconst hour = 14; if (hour < 12) { console.log('Good morning'); } else { console.log('Good afternoon'); } // Can be used in expressions (but ternary is cleaner) let greeting; if (hour < 12) { greeting = 'Good morning'; } else { greeting = 'Good afternoon'; } // Better with ternary const greeting2 = hour < 12 ? 'Good morning' : 'Good afternoon';
The else if Chain
javascriptconst 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'); } // Evaluates top to bottom, stops at first true condition const value = 15; if (value > 10) { console.log('Greater than 10'); // This runs } else if (value > 5) { console.log('Greater than 5'); // Skipped even though true }
Nested Conditionals
javascriptconst isLoggedIn = true; const isAdmin = false; if (isLoggedIn) { if (isAdmin) { console.log('Welcome, Admin!'); } else { console.log('Welcome, User!'); } } else { console.log('Please log in'); } // Can often be simplified with logical operators if (isLoggedIn && isAdmin) { console.log('Welcome, Admin!'); } else if (isLoggedIn) { console.log('Welcome, User!'); } else { console.log('Please log in'); }
Truthy and Falsy in Conditions
javascript// Falsy values: false, 0, '', null, undefined, NaN const value = ''; if (value) { console.log('Truthy'); } else { console.log('Falsy'); // This runs } // Common patterns const user = null; if (user) { console.log(user.name); // Safe, won't run if user is null } const items = []; if (items.length) { console.log('Has items'); // Won't run for empty array } // Be careful with 0 const count = 0; if (count) { console.log('Has count'); // Won't run even though 0 is valid } // Better if (count !== undefined) { console.log('Count is defined'); }
The switch Statement
Switch statements compare a value against multiple cases.
javascriptconst day = 'Monday'; switch (day) { case 'Monday': console.log('Start of 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'); }
The break Statement
javascript// Without break, execution "falls through" const grade = 'B'; switch (grade) { case 'A': console.log('Excellent!'); // Missing break - falls through! case 'B': console.log('Good job!'); // Missing break - falls through! case 'C': console.log('Well done'); break; default: console.log('Keep trying'); } // Output: 'Good job!' then 'Well done' // Intentional fall-through (document it) switch (grade) { case 'A': case 'B': case 'C': console.log('Passing grade'); break; case 'D': case 'F': console.log('Needs improvement'); break; }
Switch with Expressions
javascript// Switch uses strict equality (===) const value = '1'; switch (value) { case 1: console.log('Number 1'); break; case '1': console.log('String 1'); // This runs break; } // Switch can be used with expressions const temperature = 35; switch (true) { case temperature < 0: console.log('Freezing'); break; case temperature < 10: console.log('Cold'); break; case temperature < 20: console.log('Cool'); break; case temperature < 30: console.log('Warm'); break; default: console.log('Hot'); }
Object Lookup Alternative
javascript// Often cleaner than switch for simple mappings const dayMessages = { Monday: 'Start of work week', Tuesday: 'Second day', Wednesday: 'Hump day', Thursday: 'Almost Friday', Friday: 'TGIF!', Saturday: 'Weekend!', Sunday: 'Weekend!' }; const day = 'Friday'; console.log(dayMessages[day] || 'Invalid day');
Loops
Loops repeat code until a condition is met.
The for Loop
javascript// Basic for loop for (let i = 0; i < 5; i++) { console.log(i); // 0, 1, 2, 3, 4 } // Loop components // for (initialization; condition; update) for (let i = 10; i > 0; i -= 2) { console.log(i); // 10, 8, 6, 4, 2 } // Multiple variables for (let i = 0, j = 10; i < j; i++, j--) { console.log(`i: ${i}, j: ${j}`); } // i: 0, j: 10 // i: 1, j: 9 // ... // i: 4, j: 6 // Infinite loop (be careful!) // for (;;) { console.log('Forever'); } // Iterating arrays const fruits = ['apple', 'banana', 'cherry']; for (let i = 0; i < fruits.length; i++) { console.log(fruits[i]); }
The for...of Loop
javascript// Iterates over iterable values const colors = ['red', 'green', 'blue']; for (const color of colors) { console.log(color); // red, green, blue } // Works with strings const word = 'Hello'; for (const char of word) { console.log(char); // H, e, l, l, o } // Works with any iterable const set = new Set([1, 2, 3]); for (const value of set) { console.log(value); // 1, 2, 3 } // Getting index with entries() for (const [index, color] of colors.entries()) { console.log(`${index}: ${color}`); } // 0: red // 1: green // 2: blue
The for...in Loop
javascript// Iterates over enumerable property keys const person = { name: 'Alice', age: 30, city: 'Boston' }; for (const key in person) { console.log(`${key}: ${person[key]}`); } // name: Alice // age: 30 // city: Boston // WARNING: Includes inherited properties function Animal(name) { this.name = name; } Animal.prototype.speak = function() {}; const dog = new Animal('Rex'); for (const key in dog) { console.log(key); // name, speak (inherited!) } // Filter with hasOwnProperty for (const key in dog) { if (dog.hasOwnProperty(key)) { console.log(key); // name (only own properties) } } // Better alternatives for objects Object.keys(person).forEach(key => { console.log(`${key}: ${person[key]}`); }); Object.entries(person).forEach(([key, value]) => { console.log(`${key}: ${value}`); });
The while Loop
javascript// Basic while loop let count = 0; while (count < 5) { console.log(count); count++; } // Useful when iteration count is unknown function findIndex(array, target) { let i = 0; while (i < array.length) { if (array[i] === target) { return i; } i++; } return -1; } // Reading until condition let input = getInput(); while (input !== 'quit') { processInput(input); input = getInput(); }
The do...while Loop
javascript// Executes at least once, then checks condition let count = 0; do { console.log(count); count++; } while (count < 5); // Useful for menu-driven programs let choice; do { choice = showMenu(); handleChoice(choice); } while (choice !== 'exit'); // Executes once even if condition is false let x = 10; do { console.log(x); // 10 (runs once) } while (x < 5);
Loop Control
The break Statement
javascript// Exit loop immediately for (let i = 0; i < 10; i++) { if (i === 5) { break; } console.log(i); // 0, 1, 2, 3, 4 } // Break in while let i = 0; while (true) { if (i >= 5) break; console.log(i++); } // Break only exits innermost loop for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (j === 1) break; console.log(`${i}, ${j}`); } } // 0, 0 // 1, 0 // 2, 0
The continue Statement
javascript// Skip to next iteration for (let i = 0; i < 5; i++) { if (i === 2) { continue; } console.log(i); // 0, 1, 3, 4 } // Skip even numbers for (let i = 0; i < 10; i++) { if (i % 2 === 0) continue; console.log(i); // 1, 3, 5, 7, 9 }
Labeled Statements
javascript// Labels allow breaking/continuing outer loops outer: for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (i === 1 && j === 1) { break outer; // Breaks outer loop } console.log(`${i}, ${j}`); } } // 0, 0 // 0, 1 // 0, 2 // 1, 0 // Continue with label outer: for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (j === 1) { continue outer; // Continues outer loop } console.log(`${i}, ${j}`); } } // 0, 0 // 1, 0 // 2, 0
Modern Iteration Methods
Often, array methods are cleaner than loops.
forEach
javascriptconst numbers = [1, 2, 3, 4, 5]; numbers.forEach((num, index) => { console.log(`${index}: ${num}`); }); // Cannot break out of forEach // Use traditional loop or some/every if you need early exit
map, filter, reduce
javascriptconst numbers = [1, 2, 3, 4, 5]; // Transform each element const doubled = numbers.map(n => n * 2); console.log(doubled); // [2, 4, 6, 8, 10] // Filter elements const evens = numbers.filter(n => n % 2 === 0); console.log(evens); // [2, 4] // Reduce to single value const sum = numbers.reduce((acc, n) => acc + n, 0); console.log(sum); // 15 // Chaining const result = numbers .filter(n => n % 2 !== 0) // [1, 3, 5] .map(n => n * 2) // [2, 6, 10] .reduce((a, b) => a + b); // 18
some and every
javascriptconst numbers = [1, 2, 3, 4, 5]; // Check if any element matches (exits early) const hasEven = numbers.some(n => n % 2 === 0); console.log(hasEven); // true // Check if all elements match (exits early) const allPositive = numbers.every(n => n > 0); console.log(allPositive); // true // Useful for validation const users = [ { name: 'Alice', active: true }, { name: 'Bob', active: true }, { name: 'Charlie', active: false } ]; const allActive = users.every(user => user.active); const anyActive = users.some(user => user.active);
find and findIndex
javascriptconst users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' } ]; // Find first matching element const user = users.find(u => u.id === 2); console.log(user); // { id: 2, name: 'Bob' } // Find index of first match const index = users.findIndex(u => u.id === 2); console.log(index); // 1 // Returns undefined/-1 if not found const notFound = users.find(u => u.id === 999); console.log(notFound); // undefined
Exception Handling
try...catch
javascripttry { // Code that might throw const result = riskyOperation(); console.log(result); } catch (error) { // Handle the error console.error('Something went wrong:', error.message); } // Catch specific error types try { JSON.parse('invalid json'); } catch (error) { if (error instanceof SyntaxError) { console.log('Invalid JSON'); } else { throw error; // Re-throw unexpected errors } }
finally
javascript// finally always runs function readFile() { const file = openFile(); try { return processFile(file); } catch (error) { console.error('Error processing file'); return null; } finally { closeFile(file); // Always runs, even after return } } // Cleanup pattern let connection; try { connection = database.connect(); connection.query('SELECT * FROM users'); } catch (error) { console.error('Database error'); } finally { if (connection) { connection.close(); } }
throw Statement
javascript// Throw built-in errors function divide(a, b) { if (b === 0) { throw new Error('Division by zero'); } return a / b; } // Throw custom errors class ValidationError extends Error { constructor(message) { super(message); this.name = 'ValidationError'; } } function validateAge(age) { if (typeof age !== 'number') { throw new ValidationError('Age must be a number'); } if (age < 0 || age > 150) { throw new ValidationError('Age must be between 0 and 150'); } return true; } try { validateAge(-5); } catch (error) { if (error instanceof ValidationError) { console.log('Validation failed:', error.message); } else { throw error; } }
Summary
Conditional statements:
if,else if,elsefor branching logicswitchfor multiple case comparisons- Ternary operator for simple conditional expressions
Loops:
forwhen you know the number of iterationsfor...offor iterating values of iterablesfor...infor object keys (with caution)whilewhen condition determines iterationsdo...whilewhen you need at least one iteration
Loop control:
breakto exit loops earlycontinueto skip iterations- Labels for nested loop control
Modern alternatives:
- Array methods (
forEach,map,filter,reduce) often replace loops some,every,find,findIndexfor searching
Questions to Think About
- When should you use
switchvsif...else?
Use
switch when comparing one value against many specific cases. Use if...else for complex conditions, ranges, or when cases aren't discrete values. Object lookup is often cleaner than both for simple mappings.- Why is
for...ofpreferred overfor...infor arrays?
for...in iterates over all enumerable properties, including inherited ones and non-index properties. for...of iterates over values and works with all iterables, not just arrays.- When should you use array methods instead of loops?
Array methods like
map, filter, and reduce are preferred when you're transforming data and want to write declarative, functional code. Traditional loops are better when you need break/continue or performance is critical.- How does the
finallyblock work withreturn?
finally always executes, even if try or catch contain a return statement. The return value is preserved, but finally runs before the function actually returns.- What's the difference between
breakandcontinue?
break exits the loop entirely. continue skips the rest of the current iteration and moves to the next one.Next: Part 5 - Functions Fundamentals, where we explore function declarations, expressions, arrow functions, and scope.