Programming Fundamentals

Control Flow - Making Decisions

3 min read
Focus: PROGRAMMING-FUNDAMENTALS

TL;DR — Quick Summary

  • Six falsy values: false, 0, '', null, undefined, NaN — everything else is truthy.
  • Use if/else for complex conditions; use switch for 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, switch, and ternary
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 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
Guard Clauses — Reducing Nesting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// ❌ 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

1

Prefer lookup objects or Maps over long switch statements: const labels = { pending: '⏳', shipped: '🚚' }; labels[status] ?? '❓' — more maintainable and extensible.

2

Guard clauses are one of the most impactful readability improvements you can make — always check edge cases first and return early.

3

In React and modern frameworks, conditional rendering uses ternary and short-circuit patterns heavily: {isLoading ? <Spinner /> : <Content />}.

Common Developer Pitfalls

!
Forgetting break 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 if/else when either branch needs multiple statements.
!
Checking truthy/falsy when you mean strict equality — if (count) treats 0 as false even when 0 is a valid count. Be explicit: if (count !== null).
!
Not handling the 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 && } 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.

=== 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

1

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.

2

Create a shipping cost calculator using switch: 'local' = $0, 'national' = $5.99, 'international' = $19.99, 'express' = $29.99.

3

Refactor a deeply nested if/else function into guard clauses — verify the behaviour doesn't change.

4

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

Subscription tiers: Show features available for free/basic/pro/enterprise plans. Use switch for the tier check.
Form validation: Validate a registration form — guard-clause each field (required, format, length) and return the first error found.
Traffic light system: Given a colour ('red'/'yellow'/'green'), return the appropriate driver action using switch with a default for invalid input.
Discount logic: Apply different discount rates based on: member status, order total, and time of year — combine with logical operators.

Deepen Your Knowledge