Skip to content

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.

let i = 10;
if (i === 10) {
   // i is exactly 10, so this code will run
}

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 switch when comparing a single variable against multiple exact values
  • Use if/else when 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 next
  • break: 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:

let result = (condition) ? valueIfTrue : valueIfFalse;

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:

// Don't do this - adds property to ALL arrays!
Array.prototype.customProp = "oops";

let arr = [1, 2, 3];

for (let i in arr) {
    console.log(i); 
}
// Output: 0, 1, 2, "customProp" 😱

// Use for...of instead for arrays
for (let value of arr) {
    console.log(value);
}
// Output: 1, 2, 3 ✅

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:

function processMatrix() {
    for (let i = 0; i < 3; i++) {
        if (shouldSkipRow(i)) continue;
        processRow(i);
    }
}

function shouldSkipRow(i) {
    // Complex logic here
    return false;
}

function processRow(i) {
    // Process row logic
}

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/else for most conditional logic and complex conditions
  • Use switch when comparing a single value against multiple exact values
  • Always use === for exact equality checks
  • Remember break in switch cases to prevent fall-through
  • Ternary operator (? :) is useful for simple, inline conditionals

Loops:

  • for loops: Use when you know the number of iterations in advance
  • while loops: Use when iterations depend on a runtime condition
  • do-while loops: Guaranteed to execute at least once
  • continue: Skip current iteration
  • break: 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 on null/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...of over traditional for loops 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.


Assignment(s)