JavaScript Control Flow & Logic
By Seokhyeon Byun 13 min read
Note: This post is based on my old programming study notes when I taught myself.
Conditional Statements
If…Else Statements
The foundation of conditional logic in JavaScript.
// Basic if statement
const age = 25;
if (age >= 18) {
console.log("You are an adult");
}
// If...else
const score = 85;
if (score >= 90) {
console.log("Grade: A");
} else {
console.log("Grade: B or lower");
}
// Multiple conditions with else if
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";
}
}
// Nested conditions
const user = { role: "admin", isActive: true };
if (user.role === "admin") {
if (user.isActive) {
console.log("Admin access granted");
} else {
console.log("Admin account disabled");
}
} else {
console.log("Regular user access");
}
// Block statements with multiple operations
const temperature = 30;
if (temperature > 25) {
console.log("It's hot outside");
console.log("Consider wearing light clothes");
console.log("Stay hydrated");
}
Key Points:
- Condition must evaluate to truthy/falsy value
- Use curly braces
{}for multiple statements - Nested if statements can become complex - consider alternatives
Ternary Operator
Concise conditional expressions for simple if…else logic.
// Basic ternary syntax: condition ? expressionA : expressionB
const age = 20;
const status = age >= 18 ? "adult" : "minor";
console.log(status); // "adult"
// Ternary vs if...else comparison
// Ternary (expression - returns value)
const message = isLoggedIn ? "Welcome back!" : "Please log in";
// If...else (statement - performs action)
let message;
if (isLoggedIn) {
message = "Welcome back!";
} else {
message = "Please log in";
}
// Nested ternary (use sparingly)
const score = 85;
const grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F";
// Practical examples
function formatPrice(price, hasDiscount) {
return hasDiscount ? `$${price * 0.8} (20% off)` : `$${price}`;
}
// Ternary in JSX/template contexts
const Button = ({ isLoading }) => (
<button disabled={isLoading}>{isLoading ? "Loading..." : "Submit"}</button>
);
// Ternary with function calls
const result = isValid ? processData() : handleError();
// Multiple conditions (consider readability)
const accessLevel = isAdmin
? "full"
: isModerator
? "moderate"
: isUser
? "basic"
: "none";
Switch Statements
Efficient multi-way branching for comparing a single value against multiple cases.
// Basic switch statement
function getDayName(dayNumber) {
switch (dayNumber) {
case 0:
return "Sunday";
case 1:
return "Monday";
case 2:
return "Tuesday";
case 3:
return "Wednesday";
case 4:
return "Thursday";
case 5:
return "Friday";
case 6:
return "Saturday";
default:
return "Invalid day";
}
}
// Fall-through behavior (intentional)
function getQuarter(month) {
switch (month) {
case 1:
case 2:
case 3:
return "Q1";
case 4:
case 5:
case 6:
return "Q2";
case 7:
case 8:
case 9:
return "Q3";
case 10:
case 11:
case 12:
return "Q4";
default:
return "Invalid month";
}
}
// Fall-through demonstration
const foo = 0;
switch (foo) {
case -1:
console.log("negative 1");
break;
case 0: // Execution starts here
console.log(0);
// No break! Falls through
case 1: // This also executes
console.log(1);
break;
case 2:
console.log(2);
break;
default:
console.log("default");
}
// Output: 0, 1
// Switch with expressions and variables
function handleUserAction(action, user) {
switch (action.type) {
case "LOGIN":
return { ...user, isLoggedIn: true };
case "LOGOUT":
return { ...user, isLoggedIn: false };
case "UPDATE_PROFILE":
return { ...user, ...action.payload };
case "DELETE_ACCOUNT":
if (user.role === "admin") {
throw new Error("Cannot delete admin account");
}
return null;
default:
console.warn("Unknown action type:", action.type);
return user;
}
}
// Switch vs if...else performance
// Switch is generally faster for many conditions with simple equality checks
function processHttpStatus(status) {
switch (status) {
case 200:
return "OK";
case 201:
return "Created";
case 400:
return "Bad Request";
case 401:
return "Unauthorized";
case 403:
return "Forbidden";
case 404:
return "Not Found";
case 500:
return "Internal Server Error";
default:
return `Unknown status: ${status}`;
}
}
Key Points:
- Uses strict equality (
===) for comparisons - Each case needs
breakto prevent fall-through defaultcase is optional but recommended- More efficient than if…else chains for many conditions
Loop Statements
For Loops
Traditional counting loops with initialization, condition, and increment.
// Basic for loop
for (let i = 0; i < 5; i++) {
console.log(`Iteration ${i}`);
}
// Different initialization patterns
for (let i = 0, j = 10; i < j; i++, j--) {
console.log(`i: ${i}, j: ${j}`);
}
// Decrementing loop
for (let i = 10; i > 0; i--) {
console.log(`Countdown: ${i}`);
}
// Looping through arrays
const fruits = ["apple", "banana", "orange"];
for (let i = 0; i < fruits.length; i++) {
console.log(`${i}: ${fruits[i]}`);
}
// Complex initialization with ternary operator
for (let i = ("start" in window) ? window.start : 0; i < 9; i++) {
console.log(i);
}
// Infinite loop (be careful!)
// for (;;) {
// console.log("This runs forever!");
// break; // Need an exit condition
// }
// Nested loops for 2D operations
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
console.log(`matrix[${row}][${col}] = ${matrix[row][col]}`);
}
}
// For...in loop (iterates over object properties)
const person = { name: "John", age: 30, city: "New York" };
for (const key in person) {
console.log(`${key}: ${person[key]}`);
}
// For...of loop (iterates over iterable values)
const colors = ["red", "green", "blue"];
for (const color of colors) {
console.log(color);
}
// For...of with index using entries()
for (const [index, color] of colors.entries()) {
console.log(`${index}: ${color}`);
}
// For...of with objects (using Object.entries)
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
While Loops
Condition-based loops that continue while a condition is true.
// Basic while loop
let count = 0;
while (count < 5) {
console.log(`Count: ${count}`);
count++;
}
// While loop with complex condition
let attempts = 0;
let success = false;
while (!success && attempts < 3) {
console.log(`Attempt ${attempts + 1}`);
success = Math.random() > 0.5; // Simulate random success
attempts++;
}
// Do...while loop (executes at least once)
let userInput;
do {
userInput = prompt("Enter a number greater than 10:");
userInput = parseInt(userInput);
} while (userInput <= 10);
// Processing data until condition is met
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let index = 0;
let sum = 0;
while (index < data.length && sum < 15) {
sum += data[index];
index++;
}
console.log(`Sum: ${sum}, stopped at index: ${index}`);
// Infinite loop with break condition
let counter = 0;
while (true) {
console.log(`Processing item ${counter}`);
counter++;
if (counter >= 5) {
break; // Exit condition
}
if (counter === 2) {
continue; // Skip to next iteration
}
console.log(`Completed processing item ${counter - 1}`);
}
Control Flow Modifiers
Break and Continue
Control loop execution flow with early exit or skip mechanisms.
// Break statement - exits the loop entirely
for (let i = 0; i < 10; i++) {
if (i === 5) {
break; // Stops loop when i equals 5
}
console.log(i); // Prints 0, 1, 2, 3, 4
}
// Continue statement - skips current iteration
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) {
continue; // Skip even numbers
}
console.log(i); // Prints 1, 3, 5, 7, 9
}
// Practical example: Finding first match
function findUser(users, criteria) {
for (let i = 0; i < users.length; i++) {
const user = users[i];
let matches = true;
for (const key in criteria) {
if (user[key] !== criteria[key]) {
matches = false;
break; // No need to check other criteria
}
}
if (matches) {
return user; // Found match, exit function
}
}
return null; // No match found
}
// Break in switch statement
function processCommand(command) {
switch (command.type) {
case "start":
console.log("Starting process");
break; // Prevents fall-through
case "stop":
console.log("Stopping process");
break;
case "pause":
console.log("Pausing process");
break;
default:
console.log("Unknown command");
}
}
// Continue with conditions
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const processedNumbers = [];
for (const num of numbers) {
// Skip negative numbers
if (num < 0) continue;
// Skip even numbers
if (num % 2 === 0) continue;
// Skip numbers greater than 7
if (num > 7) continue;
processedNumbers.push(num * 2);
}
console.log(processedNumbers); // [2, 6, 14]
Recursion
Functions that call themselves to solve problems by breaking them into smaller subproblems.
// Basic recursion example - factorial
function factorial(n) {
// Base case
if (n <= 1) {
return 1;
}
// Recursive case
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120 (5 * 4 * 3 * 2 * 1)
// Fibonacci sequence
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Optimized fibonacci with memoization
function fibonacciMemo(n, memo = {}) {
if (n in memo) return memo[n];
if (n <= 1) return n;
memo[n] = fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo);
return memo[n];
}
// Tree traversal
const tree = {
value: 1,
children: [
{
value: 2,
children: [
{ value: 4, children: [] },
{ value: 5, children: [] },
],
},
{
value: 3,
children: [{ value: 6, children: [] }],
},
],
};
function traverseTree(node, depth = 0) {
console.log(" ".repeat(depth) + node.value);
for (const child of node.children) {
traverseTree(child, depth + 1);
}
}
// Array flattening
function flattenArray(arr) {
const result = [];
for (const item of arr) {
if (Array.isArray(item)) {
result.push(...flattenArray(item)); // Recursive call
} else {
result.push(item);
}
}
return result;
}
console.log(flattenArray([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]
// Binary search (recursive)
function binarySearch(arr, target, left = 0, right = arr.length - 1) {
if (left > right) return -1; // Not found
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] > target) return binarySearch(arr, target, left, mid - 1);
return binarySearch(arr, target, mid + 1, right);
}
// Tail recursion example
function countdown(n) {
if (n <= 0) {
console.log("Done!");
return;
}
console.log(n);
countdown(n - 1); // Tail call
}
// Converting recursion to iteration (stack approach)
function factorialIterative(n) {
if (n <= 1) return 1;
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
return result;
}
Key Points:
- Every recursion needs a base case to prevent infinite loops
- Recursive solutions can be elegant but may have performance costs
- Consider memoization for overlapping subproblems
- Some recursive solutions can be converted to iterative ones
Short Circuit Evaluation
Logical operators that stop evaluation once the result is determined.
// Logical AND (&&) - short circuits on false
console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false (doesn't evaluate second operand)
// Practical AND usage - conditional execution
const user = { name: "John", isAdmin: true };
user.isAdmin && console.log("Admin access granted"); // Executes
const data = null;
data && data.process(); // Doesn't execute data.process() - prevents error
// Logical OR (||) - short circuits on true
console.log(true || false); // true (doesn't evaluate second operand)
console.log(false || true); // true
console.log(false || false); // false
// Practical OR usage - default values
function greet(name) {
name = name || "Guest"; // Use 'Guest' if name is falsy
return `Hello, ${name}!`;
}
// Modern alternative with nullish coalescing
function greetModern(name) {
name = name ?? "Guest"; // Use 'Guest' only if name is null/undefined
return `Hello, ${name}!`;
}
// Chaining with short circuit evaluation
const config = {
api: {
baseUrl: "/api",
},
};
// Safe property access (pre-optional chaining)
const baseUrl = config && config.api && config.api.baseUrl;
// Modern equivalent with optional chaining
const baseUrlModern = config?.api?.baseUrl;
// Function calls with short circuit
function expensiveOperation() {
console.log("Performing expensive operation...");
return "result";
}
const shouldProcess = false;
const result = shouldProcess && expensiveOperation(); // expensiveOperation() not called
// Short circuit in conditions
function processUser(user) {
// Validate user exists and has required properties
if (!user || !user.id || !user.name) {
return "Invalid user";
}
// Process user...
return "User processed";
}
// Multiple conditions with short circuit
function canAccessFeature(user) {
return (
user &&
user.isActive &&
user.subscription &&
user.subscription.plan === "premium" &&
new Date(user.subscription.expiryDate) > new Date()
);
}
// Short circuit for caching
let expensiveResult;
function getExpensiveResult() {
return expensiveResult || (expensiveResult = performExpensiveCalculation());
}
function performExpensiveCalculation() {
console.log("Calculating...");
return "expensive result";
}
// Conditional assignment patterns
const theme = userPreferences.theme || systemTheme || "light";
const timeout = options.timeout || config.defaultTimeout || 5000;
// Guard clauses using short circuit
function divide(a, b) {
b === 0 && console.error("Division by zero!");
return b === 0 ? undefined : a / b;
}
// React-like conditional rendering pattern
const isLoading = false;
const hasError = false;
const data = ["item1", "item2"];
// Conditional rendering logic
const content =
(hasError && "Error occurred!") ||
(isLoading && "Loading...") ||
(data.length > 0 && data.map((item) => item)) ||
"No data available";
Advanced Control Flow Patterns
Switch Expression Pattern (Modern JavaScript)
// Traditional switch
function getResponseMessage(status) {
switch (status) {
case 200:
return "Success";
case 404:
return "Not Found";
case 500:
return "Server Error";
default:
return "Unknown Status";
}
}
// Object-based switch alternative
const statusMessages = {
200: "Success",
404: "Not Found",
500: "Server Error",
};
function getResponseMessageAlt(status) {
return statusMessages[status] || "Unknown Status";
}
// Map-based switch for complex cases
const actionHandlers = new Map([
["LOGIN", (user) => ({ ...user, isLoggedIn: true })],
["LOGOUT", (user) => ({ ...user, isLoggedIn: false })],
["UPDATE", (user, data) => ({ ...user, ...data })],
]);
function handleAction(action, user, data) {
const handler = actionHandlers.get(action);
return handler ? handler(user, data) : user;
}
Guard Clauses Pattern
// Instead of nested if statements
function processUserBad(user) {
if (user) {
if (user.isActive) {
if (user.permissions) {
if (user.permissions.canRead) {
// Process user
return "User processed";
} else {
return "No read permission";
}
} else {
return "No permissions";
}
} else {
return "User inactive";
}
} else {
return "No user provided";
}
}
// Use guard clauses for early returns
function processUserGood(user) {
if (!user) return "No user provided";
if (!user.isActive) return "User inactive";
if (!user.permissions) return "No permissions";
if (!user.permissions.canRead) return "No read permission";
// Process user
return "User processed";
}