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/elsefor complex logic,switchfor discrete values, ternary for inline expressions - Loops:
for(known count),while(condition-based),do-while(at least once) - Enhanced loops:
for...offor iterables (arrays, strings),for...infor object properties,for await...offor async - Jump statements:
breakexits loops/switch,continueskips to next iteration,returnexits functions - Exception handling:
try-catch-finallyfor error handling;throwto raise errors;finallyalways executes - Labels: Enable break/continue to target outer loops; useful for nested structures but consider function refactoring