Control Flow - Making Decisions
TL;DR — Quick Summary
- Six falsy values:
false,0,'',null,undefined,NaN— everything else is truthy. - Use
if/elsefor complex conditions; useswitchfor matching one value against many exact cases. - Ternary (
? :) returns a value — use it for simple inline conditions, not multi-line logic. - Guard clauses (early returns for invalid cases) reduce nesting and make the happy path obvious.
Lesson Overview
Control flow is what makes programs intelligent — the ability to execute different code depending on conditions. Without it, every program would do exactly the same thing every time.
JavaScript provides three main decision-making tools: if/else if/else for condition chains, switch for matching a single value against many cases, and the ternary operator for concise single-expression decisions. Choosing the right tool for each situation makes code cleaner and more maintainable.
Understanding truthy and falsy values is essential — any value can be used in a condition, not just booleans. The six falsy values (0, '', null, undefined, false, NaN) evaluate to false; everything else is truthy.
Conceptual Deep Dive
Truthy and falsy values:
- Falsy:
false,0,-0,0n,'',null,undefined,NaN - Truthy: everything else — including
'false'(non-empty string),[](empty array),{}(empty object),-1
if/else: Execute different blocks based on boolean conditions. Chain with else if for multiple branches. Conditions are evaluated top-to-bottom — the first truthy one wins.
switch: Match a single value against multiple cases using strict equality. Always include break unless intentional fall-through. The default case handles unmatched values.
Ternary operator condition ? valueIfTrue : valueIfFalse: Returns a value — unlike if/else. Best for concise single-expression decisions, not complex multi-line logic.
Guard clauses: A pattern that checks for invalid/edge cases early and returns, reducing nesting depth and improving readability.
Implementation Lab
// if / else if / elseelse if / else
const temperature = 72;
if (temperature >= 90) {
console.log('🔥 Hot — wear light clothes');
} else if (temperature >= 70) {
console.log('😊 Comfortable — light jacket optional');
} else if (temperature >= 50) {
console.log('🧥 Cool — jacket recommended');
} else {
console.log('🥶 Cold — heavy coat needed');
}
// Output: 😊 Comfortable — light jacket optionalComfortable — light jacket optional
// switch — ideal for matching a single value
const orderStatus = 'shipped';
switch (orderStatus) {
case 'pending':
console.log('⏳ Processing your order...');
break;
case 'processing':
console.log('📦 Packing your items...');
break;
case 'shipped':
console.log('🚚 On the way! Track your package.'Track your package.');
break;
case 'delivered':
console.log('✅ Delivered. Please leave a review!'Please leave a review!');
break;
default:
console.log('❓ Unknown status. Contact support.'Contact support.');
}
// Ternary operator — concise conditional expression
const isLoggedIn = true;
const greeting = isLoggedIn ? 'Welcome back!' : 'Please log in';
console.log(greeting); // 'Welcome back!'
// Avoid nested ternaries — they become unreadable
// ❌ Bad
const label = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : 'F';
// ✅ Better — use if/else or a lookup object for multiple casesif/else or a lookup object for multiple cases// ❌ Deeply nested (pyramid of doom)(pyramid of doom)
function processPayment(user, cart, paymentMethod) {
if (user) {
if (cart.items.length > 0) {
if (paymentMethod.isValid) {
if (user.balance >= cart.total) {
// Finally, the actual logic...
return processCharge(user, cart.total);
} else {
return { error: 'Insufficient balance' };
}
} else {
return { error: 'Invalid payment method' };
}
} else {
return { error: 'Cart is empty' };
}
} else {
return { error: 'User not found' };
}
}
// ✅ Guard clauses — return early for invalid casesreturn early for invalid cases
function processPayment(user, cart, paymentMethod) {
if (!user) return { error: 'User not found' };
if (cart.items.length === 0) return { error: 'Cart is empty' };
if (!paymentMethod.isValid) return { error: 'Invalid payment method' };
if (user.balance < cart.total) return { error: 'Insufficient balance' };
// Happy path: clean, unnested, clear
return processCharge(user, cart.total);
}Pro Tips — Senior Dev Insights
Prefer lookup objects or Maps over long switch statements: const labels = { pending: '⏳', shipped: '🚚' }; labels[status] ?? '❓' — more maintainable and extensible.
Guard clauses are one of the most impactful readability improvements you can make — always check edge cases first and return early.
In React and modern frameworks, conditional rendering uses ternary and short-circuit patterns heavily: {isLoading ? <Spinner /> : <Content />}.
Common Developer Pitfalls
break in switch cases — without it, execution falls through to the next case, which is almost never intended.if/else when either branch needs multiple statements.if (count) treats 0 as false even when 0 is a valid count. Be explicit: if (count !== null).default case in switch — always include a default to handle unexpected values gracefully.Interview Mastery
Falsy values are: false, 0, -0, 0n (BigInt zero), '' (empty string), null, undefined, and NaN — exactly 8 values. Everything else is truthy, including '0' (string zero), 'false' (the string), [] (empty array), {} (empty object), and -1. This matters in conditionals: if ('') is false, if ([]) is true. Common bug: if (arr.length) fails when length is 0, which is correct, but if (value) can give wrong results when 0 or '' are valid values.
switch is best when: comparing a single variable against multiple exact values (status codes, command strings, enum-like values), all cases use === comparison, and you want to group cases (multiple cases sharing one block). if/else is better when: conditions involve ranges or complex expressions (age > 18 && isVerified), different variables are compared in each branch, or you need logical operators. In modern JavaScript, a lookup object or Map is often cleaner than both for simple value-to-action mapping.
Short-circuit evaluation means JavaScript stops evaluating a logical expression once the result is determined. && stops at the first falsy value; || stops at the first truthy value. In control flow: isLoggedIn && renderDashboard() conditionally calls a function without an if statement. In React JSX: {isLoading &&
=== checks both value and type without any coercion. == performs type coercion before comparing. Examples: 5 == '5' is true (coerces string to number), 0 == false is true (coerces false to 0), null == undefined is true. These coercions make == unpredictable — code that works for one type silently breaks for another. Always use ===. The one legitimate use of == is null checking: value == null matches both null and undefined in one check.
Hands-on Lab Exercises
Build a grade calculator: take a numeric score (0–100) and return a letter grade (A/B/C/D/F) and descriptive message using if/else.
Create a shipping cost calculator using switch: 'local' = $0, 'national' = $5.99, 'international' = $19.99, 'express' = $29.99.
Refactor a deeply nested if/else function into guard clauses — verify the behaviour doesn't change.
Create a role-based access control function: returns what each role (admin, editor, viewer, guest) can do using a lookup object instead of switch.
Real-World Practice Scenarios
Deepen Your Knowledge
Control Flow - Making Decisions
TL;DR — Quick Summary
- Six falsy values:
false,0,'',null,undefined,NaN— everything else is truthy. - Use
if/elsefor complex conditions; useswitchfor matching one value against many exact cases. - Ternary (
? :) returns a value — use it for simple inline conditions, not multi-line logic. - Guard clauses (early returns for invalid cases) reduce nesting and make the happy path obvious.
Overview
Control flow is what makes programs intelligent — the ability to execute different code depending on conditions. Without it, every program would do exactly the same thing every time.
JavaScript provides three main decision-making tools: if/else if/else for condition chains, switch for matching a single value against many cases, and the ternary operator for concise single-expression decisions. Choosing the right tool for each situation makes code cleaner and more maintainable.
Understanding truthy and falsy values is essential — any value can be used in a condition, not just booleans. The six falsy values (0, '', null, undefined, false, NaN) evaluate to false; everything else is truthy.
Deep Dive Analysis
<p><strong>Truthy and falsy values:</strong></p><ul><li>Falsy: <code>false</code>, <code>0</code>, <code>-0</code>, <code>0n</code>, <code>''</code>, <code>null</code>, <code>undefined</code>, <code>NaN</code></li><li>Truthy: everything else — including <code>'false'</code> (non-empty string), <code>[]</code> (empty array), <code>{}</code> (empty object), <code>-1</code></li></ul><p><strong>if/else:</strong> Execute different blocks based on boolean conditions. Chain with <code>else if</code> for multiple branches. Conditions are evaluated top-to-bottom — the first truthy one wins.</p><p><strong>switch:</strong> Match a single value against multiple cases using strict equality. Always include <code>break</code> unless intentional fall-through. The <code>default</code> case handles unmatched values.</p><p><strong>Ternary operator</strong> <code>condition ? valueIfTrue : valueIfFalse</code>: Returns a value — unlike if/else. Best for concise single-expression decisions, not complex multi-line logic.</p><p><strong>Guard clauses:</strong> A pattern that checks for invalid/edge cases early and returns, reducing nesting depth and improving readability.</p>
Implementation Reference
// if / else if / else
const temperature = 72;
if (temperature >= 90) {
console.log('🔥 Hot — wear light clothes');
} else if (temperature >= 70) {
console.log('😊 Comfortable — light jacket optional');
} else if (temperature >= 50) {
console.log('🧥 Cool — jacket recommended');
} else {
console.log('🥶 Cold — heavy coat needed');
}
// Output: 😊 Comfortable — light jacket optional
// switch — ideal for matching a single value
const orderStatus = 'shipped';
switch (orderStatus) {
case 'pending':
console.log('⏳ Processing your order...');
break;
case 'processing':
console.log('📦 Packing your items...');
break;
case 'shipped':
console.log('🚚 On the way! Track your package.');
break;
case 'delivered':
console.log('✅ Delivered. Please leave a review!');
break;
default:
console.log('❓ Unknown status. Contact support.');
}
// Ternary operator — concise conditional expression
const isLoggedIn = true;
const greeting = isLoggedIn ? 'Welcome back!' : 'Please log in';
console.log(greeting); // 'Welcome back!'
// Avoid nested ternaries — they become unreadable
// ❌ Bad
const label = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : 'F';
// ✅ Better — use if/else or a lookup object for multiple cases// ❌ Deeply nested (pyramid of doom)
function processPayment(user, cart, paymentMethod) {
if (user) {
if (cart.items.length > 0) {
if (paymentMethod.isValid) {
if (user.balance >= cart.total) {
// Finally, the actual logic...
return processCharge(user, cart.total);
} else {
return { error: 'Insufficient balance' };
}
} else {
return { error: 'Invalid payment method' };
}
} else {
return { error: 'Cart is empty' };
}
} else {
return { error: 'User not found' };
}
}
// ✅ Guard clauses — return early for invalid cases
function processPayment(user, cart, paymentMethod) {
if (!user) return { error: 'User not found' };
if (cart.items.length === 0) return { error: 'Cart is empty' };
if (!paymentMethod.isValid) return { error: 'Invalid payment method' };
if (user.balance < cart.total) return { error: 'Insufficient balance' };
// Happy path: clean, unnested, clear
return processCharge(user, cart.total);
}Common Pitfalls
- •Forgetting <code>break</code> in switch cases — without it, execution falls through to the next case, which is almost never intended.
- •Using the ternary operator for complex multi-line logic — it becomes unreadable. Use <code>if/else</code> when either branch needs multiple statements.
- •Checking truthy/falsy when you mean strict equality — <code>if (count)</code> treats <code>0</code> as false even when 0 is a valid count. Be explicit: <code>if (count !== null)</code>.
- •Not handling the <code>default</code> case in switch — always include a <code>default</code> to handle unexpected values gracefully.
Hands-on Practice
- ✓Build a grade calculator: take a numeric score (0–100) and return a letter grade (A/B/C/D/F) and descriptive message using if/else.
- ✓Create a shipping cost calculator using switch: 'local' = $0, 'national' = $5.99, 'international' = $19.99, 'express' = $29.99.
- ✓Refactor a deeply nested if/else function into guard clauses — verify the behaviour doesn't change.
- ✓Create a role-based access control function: returns what each role (admin, editor, viewer, guest) can do using a lookup object instead of switch.
Expert Pro Tips
Interview Preparation
Q: What are truthy and falsy values? Give examples.
Master Answer:
Falsy values are: false, 0, -0, 0n (BigInt zero), '' (empty string), null, undefined, and NaN — exactly 8 values. Everything else is truthy, including '0' (string zero), 'false' (the string), [] (empty array), {} (empty object), and -1. This matters in conditionals: if ('') is false, if ([]) is true. Common bug: if (arr.length) fails when length is 0, which is correct, but if (value) can give wrong results when 0 or '' are valid values.
Q: When should you use switch vs if/else?
Master Answer:
switch is best when: comparing a single variable against multiple exact values (status codes, command strings, enum-like values), all cases use === comparison, and you want to group cases (multiple cases sharing one block). if/else is better when: conditions involve ranges or complex expressions (age > 18 && isVerified), different variables are compared in each branch, or you need logical operators. In modern JavaScript, a lookup object or Map is often cleaner than both for simple value-to-action mapping.
Q: Explain short-circuit evaluation and how it's used in control flow.
Master Answer:
Short-circuit evaluation means JavaScript stops evaluating a logical expression once the result is determined. && stops at the first falsy value; || stops at the first truthy value. In control flow: isLoggedIn && renderDashboard() conditionally calls a function without an if statement. In React JSX: {isLoading && <Spinner />} renders the spinner only when isLoading is true. Default values: const name = user.name || 'Guest'. Modern improvement: use ?? instead of || for defaults when 0 or '' are valid values.
Q: What is === vs ==? Why should you use ===?
Master Answer:
=== checks both value and type without any coercion. == performs type coercion before comparing. Examples: 5 == '5' is true (coerces string to number), 0 == false is true (coerces false to 0), null == undefined is true. These coercions make == unpredictable — code that works for one type silently breaks for another. Always use ===. The one legitimate use of == is null checking: value == null matches both null and undefined in one check.
Simulated Scenarios
Extended Reading
MDN: if...else
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
MDN: switch
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch
JavaScript.info: Conditional branching
https://javascript.info/ifelse
© 2026 DevHub Engineering • All Proprietary Rights Reserved
Generated on March 7, 2026 • Ver: 4.0.2
Document Class: Master Education
Confidential Information • Licensed to User