Flow
In programming, “flow” refers to the sequence in which instructions or statements are executed. The ability to control this sequence, based on certain conditions, allows programs to be dynamic and adaptable to different situations. This is where flow commands, or control structures, come into play.
Flow commands in JavaScript, like in many programming languages, determine the sequence in which code is executed, enabling conditional operations and repetitive tasks. Without flow control, every program would execute line by line from top to bottom with no ability to make decisions or repeat actions - making it impossible to build anything beyond the simplest scripts.
Conditions
Conditional statements allow your program to make decisions and execute different code blocks based on whether specific conditions are true or false. This is fundamental to creating dynamic, responsive applications.
if
The if statement executes a block of code only when a specified condition evaluates to true. This is the most basic form of decision-making in programming.
graph LR
A[Start] --> B{if?};
B -->|yes| C[...];
B -->|no| D[...];
Basic usage:
if (true) {
// code here will run
}
if (true && true) {
// both conditions are true, so this code will run
}
if ((true && true) || true) {
// one of the conditions is true, so this code will run
}
Working with truthy/falsy values:
JavaScript has the concept of “truthy” and “falsy” values. The following values are falsy: false, 0, "" (empty string), null, undefined, and NaN. Everything else is truthy.
// Remember - truthy/falsy values
if (!null) {
// null is falsy, so this code will run
}
if (!undefined) {
// undefined is falsy, so this code will run
}
if (!0) {
// 0 is falsy, so this code will run
}
if (!"") {
// an empty string is falsy, so this code will run
}
Strict equality:
Always use === for exact equality checks to avoid type coercion issues.
if-else statement:
let age = 16;
if (age >= 18) {
console.log("You can vote");
} else {
console.log("Too young to vote");
}
Real-world example - form validation:
let username = getUserInput();
if (username.length === 0) {
console.log("Error: Username cannot be empty");
} else if (username.length < 3) {
console.log("Error: Username must be at least 3 characters");
} else if (username.length > 20) {
console.log("Error: Username cannot exceed 20 characters");
} else {
console.log("Valid username!");
}
switch
Switch statements provide a cleaner way to handle multiple specific conditions compared to long chains of if-else if statements. They’re particularly useful when you have a single value that needs to be compared against multiple possible cases.
When to use switch vs if/else:
- Use
switchwhen comparing a single variable against multiple exact values - Use
if/elsewhen you need complex conditions or range comparisons
flowchart LR
A[Start] --> B{switch};
B -->|case 1| C[...];
B -->|case 2| D[...];
B -->|case 3| E[...];
B -->|default| F[...];
Basic switch with break statements:
let day = 3;
switch (day) {
case 1:
console.log("Monday");
break;
case 2:
console.log("Tuesday");
break;
case 3:
console.log("Wednesday");
break;
default:
console.log("Invalid day");
}
// Output: "Wednesday"
Understanding Fall-Through Behavior
If you omit the break statement, JavaScript will continue executing the next case’s code regardless of whether it matches. This is called “fall-through” and is usually unintentional and leads to bugs.
Example of fall-through (usually a mistake):
let y = 1;
let output = "Output: ";
switch (y) {
case 0:
output += "0 ";
case 1:
output += "1 "; // matches here
case 2:
output += "2 "; // falls through
case 3:
output += "3 "; // falls through
case 4:
output += "4 "; // falls through
break; // finally stops here
case 5:
output += "5";
break;
default:
console.log("Only 0-5");
}
console.log(output); // Output: 1 2 3 4
In this example, since y is 1, the code starts at case 1: and continues through cases 2, 3, and 4 until it hits the break statement.
Intentional Fall-Through
Fall-through can be useful when multiple cases should execute the same code block. Always comment your code when using intentional fall-through!
let day = "Tuesday";
switch (day) {
case "Monday":
case "Tuesday":
case "Wednesday":
case "Thursday":
case "Friday":
console.log("It's a weekday - time to work!");
break;
case "Saturday":
case "Sunday":
console.log("It's the weekend - time to relax!");
break;
default:
console.log("Invalid day");
}
// Output: "It's a weekday - time to work!"
Real-world example - menu system:
let userChoice = getMenuSelection(); // returns 1-4
switch (userChoice) {
case 1:
displayUserProfile();
break;
case 2:
showSettings();
break;
case 3:
displayNotifications();
break;
case 4:
logout();
break;
default:
console.log("Invalid selection. Please choose 1-4.");
}
Loops
Loops allow you to execute a block of code repeatedly, which is essential for tasks like processing arrays, validating input, or performing calculations. Without loops, you’d have to write the same code over and over manually.
graph LR
A[Start] --> B(Initialization)
B --> C{Condition}
C -->|True| D(Execute code)
D --> E(Iteration)
E --> C
C -->|False| F[End]
for
The for loop is ideal when you know in advance how many times you need to iterate. It combines initialization, condition, and iteration in a single, compact statement.
Syntax breakdown:
// for (initialization; condition; iteration)
for (let i = 0; i < 10; i++) {
// Start at 0, run while i < 10, increment by 1 each time
}
Basic examples:
let s = "";
for (let i = 0; i < 10; i++) {
s += i + " ";
}
console.log(s); // 0 1 2 3 4 5 6 7 8 9
s = "";
for (let i = 0; i < 10; i += 2) {
s += i + " ";
}
console.log(s); // 0 2 4 6 8
s = "";
for (let i = 10; i > 0; i--) {
s += i + " ";
}
console.log(s); // 10 9 8 7 6 5 4 3 2 1
Using continue and break:
continue: Skip the current iteration and move to the nextbreak: Exit the loop entirely
s = "";
for (let i = 0; i < 10; i++) {
if (i === 4) continue; // skip 4
if (i === 7) break; // stop at 7
s += i + " ";
}
console.log(s); // 0 1 2 3 5 6
Real-world example - processing array data:
let temperatures = [18, 22, 19, 25, 21, 17, 20];
let sum = 0;
for (let i = 0; i < temperatures.length; i++) {
sum += temperatures[i];
}
let average = sum / temperatures.length;
console.log(`Average temperature: ${average.toFixed(1)}°C`);
while
The while loop is used when you don’t know in advance how many iterations you’ll need. It continues executing as long as the condition remains true. This is common when waiting for user input or processing data until a certain condition is met.
while vs do-while:
while: Checks condition before executing (may never execute)do-while: Executes once before checking condition (always executes at least once)
let s = "";
let i = 0;
while (i < 5) {
i++;
s += `${i} `;
}
console.log(s); // 1 2 3 4 5
// do-while guarantees at least one execution
s = "";
i = 0;
do {
i++;
s += `${i} `;
} while (i < 5);
console.log(s); // 1 2 3 4 5
// Demonstrating the difference
i = 10;
while (i < 5) {
console.log("This will never run");
}
do {
console.log("This will run once, even though condition is false");
} while (i < 5);
Using break in while loops:
s = "";
i = 0;
while (i < 5) {
i++;
s += `${i} `;
if (i === 3) break; // exit early
}
console.log(s); // 1 2 3
Real-world example - input validation:
let password = "";
let attempts = 0;
const maxAttempts = 3;
do {
password = prompt("Enter your password:");
attempts++;
if (password === "secret123") {
console.log("Access granted!");
break;
} else {
console.log(`Incorrect password. ${maxAttempts - attempts} attempts remaining.`);
}
} while (attempts < maxAttempts);
if (attempts === maxAttempts && password !== "secret123") {
console.log("Account locked. Too many failed attempts.");
}
Advanced Flow Control
The following constructs are useful in specific situations. You won’t need them in every program, but they’re valuable tools when the situation calls for them.
Ternary Operator
The ternary operator is a concise way of writing simple if-else statements. It’s particularly useful for assignments or inline conditions.
Syntax:
Examples:
let age = 16;
let type = (age >= 18) ? "Adult" : "Minor";
console.log(type); // "Minor"
// Useful in template strings
let temperature = 25;
console.log(`It's ${temperature > 20 ? "warm" : "cold"} today`);
// Can be nested (but avoid deep nesting for readability)
let score = 85;
let grade = (score >= 90) ? "A" :
(score >= 80) ? "B" :
(score >= 70) ? "C" : "F";
console.log(grade); // "B"
When to use ternary operator
Use ternary for simple, single-line conditionals. For complex logic or multiple statements, stick with traditional if-else for better readability.
Nullish Coalescing Operator (??)
The nullish coalescing operator returns the right-hand operand when the left operand is null or undefined, otherwise it returns the left operand. This is different from the logical OR operator (||).
The key difference from ||:
// Using || (checks for falsy values)
let count = 0;
let result = count || 10;
console.log(result); // 10 (0 is falsy, so 10 is used)
// Using ?? (checks only for null/undefined)
count = 0;
result = count ?? 10;
console.log(result); // 0 (0 is not null/undefined, so 0 is used)
// Practical example
let userInput = "";
let displayName = userInput || "Anonymous"; // "Anonymous" (empty string is falsy)
displayName = userInput ?? "Anonymous"; // "" (empty string is not null/undefined)
// When ?? is actually useful
let config = null;
let timeout = config ?? 5000; // 5000 (config is null)
config = { timeout: 0 };
timeout = config.timeout ?? 5000; // 0 (intentional zero timeout)
Advanced Loop Patterns
for…of loop
The for...of loop is the modern way to iterate over iterable objects like arrays and strings. It’s cleaner and less error-prone than traditional for loops when you don’t need the index.
let colors = ["red", "green", "blue"];
// Traditional for loop
for (let i = 0; i < colors.length; i++) {
console.log(colors[i]);
}
// for...of loop (cleaner)
for (let color of colors) {
console.log(color);
}
// Output: red, green, blue
// Works with strings too
let text = "Hello";
for (let char of text) {
console.log(char);
}
// Output: H, e, l, l, o
for…in loop
The for...in loop iterates over enumerable properties of an object. It’s useful for objects but should be avoided for arrays due to potential issues with prototype properties.
let person = {
name: "John",
age: 25,
city: "Copenhagen"
};
for (let key in person) {
console.log(`${key}: ${person[key]}`);
}
// Output:
// name: John
// age: 25
// city: Copenhagen
Be careful with for…in on arrays
The for...in loop can iterate over inherited properties, which can cause unexpected behavior:
Labelled Statements
Labels allow you to control nested loops with break and continue. This is an advanced pattern that’s rarely needed in modern JavaScript - most situations can be solved more clearly with functions or different loop structures.
outerLoop:
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === j) {
continue outerLoop; // skip to next iteration of outer loop
}
console.log(`i = ${i}, j = ${j}`);
}
}
// Output:
// i = 1, j = 0
// i = 2, j = 0
// i = 2, j = 1
Modern alternative to labelled statements
Instead of labelled statements, consider refactoring into separate functions for better readability:
Summary
Flow control is fundamental to programming, allowing your code to make decisions and repeat actions based on conditions.
Key Takeaways
Conditional Statements:
- Use
if/elsefor most conditional logic and complex conditions - Use
switchwhen comparing a single value against multiple exact values - Always use
===for exact equality checks - Remember
breakin switch cases to prevent fall-through - Ternary operator (
? :) is useful for simple, inline conditionals
Loops:
forloops: Use when you know the number of iterations in advancewhileloops: Use when iterations depend on a runtime conditiondo-whileloops: Guaranteed to execute at least oncecontinue: Skip current iterationbreak: Exit loop entirely
Advanced Patterns:
for...of: Iterate over array values (modern and clean)for...in: Iterate over object properties (avoid for arrays)??: Nullish coalescing - only triggers onnull/undefined, not all falsy values- Labelled statements: Rarely needed; consider refactoring into functions instead
Best Practices:
- Keep conditions simple and readable
- Avoid deep nesting - refactor into functions
- Use the simplest loop structure that fits your needs
- Comment intentional fall-through in switch statements
- Prefer
for...ofover traditionalforloops when you don’t need the index
Understanding flow control transforms your programs from static scripts into dynamic, responsive applications that can handle real-world complexity.