TL;DR
Control flow determines which parts of your program run and when. Use if, if-else, and else if to make boolean decisions. Use switch when you have many exact-match branches (like checking specific values). This article explains each structure, shows clear examples, visualizes the flow with diagrams, and gives practice assignments.
Introduction - Why control flow matters
Computers follow instructions. Control flow is the mechanism that lets your program choose between multiple paths — essentially decision-making inside code.
Real life: if it’s raining, take an umbrella; otherwise don’t. Programs need the same kind of branching logic to react differently based on input, state, or user actions.
What you'll learn:
if / if-else / else if syntax and when to use them
switch syntax and the role of break
How to pick between if-else and switch
Common beginner mistakes and best practices
Small exercises with solutions
The if statement
Purpose
Use if when you want to run a block of code only when a condition is true.
Syntax
if (condition) {
// runs when condition is truthy
}
Simple example
const isRaining = true;
if (isRaining) {
console.log("Take an umbrella");
}
What to explain while writing this section
condition is any expression that evaluates to a boolean (or to a value that coerces to boolean).
Parentheses around the condition are required. Curly braces for the block are required for multi-line blocks (single statements can omit braces but avoid that for readability).
Truthy vs falsy (brief): 0, "", null, undefined, NaN, and false are falsy; everything else is truthy.
Step-by-step trace example
const temp = 30;
if (temp > 25) {
console.log("It's warm");
}
Evaluate temp > 25 → 30 > 25 → true.
Enter the if block and run console.log("It's warm").
Common beginner mistakes
Using assignment = instead of comparison ==/===.
Forgetting braces when adding more statements later.
Relying on implicit coercion without understanding truthy/falsy.
The if-else statement
Purpose
Use if-else when you need one behavior for true and a different behavior for false.
Syntax
if (condition) {
// runs when condition is true
} else {
// runs when condition is false
}
Example
const age = 17;
if (age >= 18) {
console.log("You can vote");
} else {
console.log("You are not eligible to vote yet");
}
Step-by-step execution (trace)
Evaluate age >= 18 → 17 >= 18 → false.
Skip if block; execute else block.
Pitfalls
The else if ladder
Purpose
Use else if when you have more than two mutually exclusive branches — e.g., multiple ranges or categories.
Syntax
if (cond1) {
// ...
} else if (cond2) {
// ...
} else if (cond3) {
// ...
} else {
// fallback
}
Example: grading
const score = 76;
if (score >= 90) {
console.log("A");
} else if (score >= 75) {
console.log("B");
} else if (score >= 60) {
console.log("C");
} else {
console.log("F");
}
Execution order note
JavaScript evaluates conditions top-to-bottom and stops at the first true condition. That’s why the most specific (or highest priority) checks should come first.
Step-by-step trace for score = 76
score >= 90 → false.
score >= 75 → true → print "B" → skip rest.
Common beginner mistakes
Ordering conditions wrongly (e.g., checking x > 10 before x > 20).
Using many else ifs where a switch (or map) would be cleaner.
The switch statement
Purpose
Use switch for multiple branches based on exact matches of a single expression (often better than a long else if ladder when checking the same variable for equality against many possible values).
Syntax
switch (expression) {
case value1:
// code
break;
case value2:
// code
break;
default:
// fallback
}
Example: day of week
const 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");
}
Key points about break
break exits the switch block. Without break, execution falls through to the next case.
Fall-through can be useful intentionally (see consolidated cases below), but it’s a common source of bugs when accidental.
Intentional fall-through example
const v = "banana";
switch (v) {
case "apple":
case "banana":
case "pear":
console.log("This is a common fruit");
break;
default:
console.log("Other");
}
Here, apple, banana, and pear share the same outcome.
Common beginner mistakes
Forgetting break, causing unexpected fall-through.
Using complex expressions in case labels — they are matched with strict equality (===-like).
Expecting switch to work with ranges — it doesn't (unless you use tricks like true in switch).
Trick: switch(true) for ranges (advanced)
Not recommended for beginners, but useful to know:
const score = 85;
switch (true) {
case score >= 90:
console.log("A");
break;
case score >= 75:
console.log("B");
break;
default:
console.log("F");
}
This works because switch compares the expression (here true) to each case expression using strict equality.
When to use switch vs if-else
Quick heuristics
Examples
HTTP status code handling: switch (statusCode) { case 200: ... }
Small set of commands (e.g., CLI): switch (command) { ... }
Ranges like grades or thresholds: prefer if-else.
Readability and maintainability
If your else if ladder is long and each branch checks the same variable for equality, switch is clearer. Conversely, prefer if-else for flexible conditions.
Common pitfalls and best practices
Pitfalls
Using = instead of === or ==. Always use === unless you have a specific reason to coerce.
Forgetting break in switch.
Over-nesting conditionals — deeply nested ifs are hard to read.
Long else if ladders that check different variables — refactor into separate functions or use a Map.
Best practices
Prefer explicit comparisons (age >= 18) over relying on truthy/falsy for user-facing logic.
Return early to keep code flat:
function handle(x) {
if (!x) return;
// rest of logic
}
Use switch for many exact-value checks; use if for boolean expressions and ranges.
Comment intentional fall-through in switches clearly:
case 1:
// fall-through intentional
case 2:
doSomething();
break;
Quick reference (cheat-sheet)
if (cond) {...} — run block when cond truthy.
if (cond) {...} else {...} — choose between two branches.
if (a) {...} else if (b) {...} else {...} — chain multiple checks.
switch (expr) { case v: ... break; default: ... } — best for many exact matches.
Use === for equality. Use break to avoid fall-through. Comment intentional fall-through.
Conclusion and next steps
Control flow is foundational. Once you're comfortable with if/else and switch, practice by:
Adding input validation (bad inputs → show friendly messages)
Rewriting switch logic as a Map lookup when values map to functions or text
Avoiding deep nesting: refactor with functions or early returns
Try small projects like a CLI menu, form validation, or a simple rule-based game
Using a Map instead of switch (refactor)
const dayMap = new Map([
[0, "Sunday"],
[1, "Monday"],
[2, "Tuesday"],
[3, "Wednesday"],
[4, "Thursday"],
[5, "Friday"],
[6, "Saturday"]
]);
function dayName(dayNumber) {
return dayMap.get(dayNumber) ?? "Invalid day number";
}
This scales better when you need to store extra metadata or functions per case.
Early-return pattern to reduce nesting
function process(user) {
if (!user) return "No user";
if (!user.active) return "Inactive user";
// continue with core logic
}