JavaScript Conditionals and Control Flow — if, switch, Ternary, and the Modern Nullish Toolkit

A practical guide to branching in modern JavaScript — truthy and falsy rules, if / else if / else, switch, the ternary operator, short-circuit evaluation, optional chaining (?.), and nullish coalescing (??).

Why this still matters

Branching is the cheapest way to introduce subtle bugs in any language, and JavaScript adds a few of its own. Loose equality, the unique definition of truthy, the difference between undefined and null, and several decades of habits picked up from other languages all conspire to make if statements harder than they look.

This post brings the basics together with the modern operators (??, ?., logical assignment) that often let you delete a conditional entirely.

If you have not yet read JavaScript variables, scope, and hoisting, start there — it explains the binding rules that conditionals rely on.

Truthy and falsy — the eight values to remember

Every value in JavaScript is either truthy (treated as true in a boolean context) or falsy. There are exactly eight falsy values; everything else is truthy.

Falsy valueNotes
false
0
-0Same as 0 in almost every context.
0nThe BigInt zero.
"" (empty string)' ' (a space) is truthy.
null
undefined
NaN

if / else if / else

The workhorse. Keep them shallow — nested conditionals are the single biggest readability killer.

 1function shippingBand(weightKg) {
 2  if (weightKg <= 0) {
 3    throw new RangeError("weight must be positive");
 4  } else if (weightKg < 1) {
 5    return "small";
 6  } else if (weightKg < 10) {
 7    return "medium";
 8  } else {
 9    return "large";
10  }
11}

Prefer early return over deep nesting

 1// Hard to read
 2function process(user) {
 3  if (user) {
 4    if (user.active) {
 5      if (user.email) {
 6        sendWelcome(user.email);
 7      }
 8    }
 9  }
10}
11
12// Much clearer
13function process(user) {
14  if (!user)         return;
15  if (!user.active)  return;
16  if (!user.email)   return;
17  sendWelcome(user.email);
18}

The equality operators — always use ===

JavaScript inherited two equality operators: == (loose, performs type coercion) and === (strict, no coercion). The loose form is the source of an entire genre of bugs.

10 == "0";          // true   ← surprising
20 == [];           // true   ← very surprising
3"" == false;       // true
4null == undefined; // true   ← the one useful loose check
5null === undefined;// false
6
70 === "0";         // false  ← always what you want

The ternary operator

A pure expression that picks between two values. Excellent for one-line assignments; resist the urge to chain them.

1const label = isPremium ? "Premium" : "Standard";
2
3// Acceptable when each branch is a value
4const fee = country === "US" ? 5
5          : country === "UK" ? 4
6          : 8;
7
8// Almost always better as an if / else

For more than two cases, lift the mapping into a lookup object:

1const fees = { US: 5, UK: 4 };
2const fee  = fees[country] ?? 8;

switch — useful, with caveats

switch compares with === and falls through unless you break (or return). Group cases by letting them fall through deliberately:

 1function categorise(status) {
 2  switch (status) {
 3    case "draft":
 4    case "review":
 5      return "in-progress";
 6    case "published":
 7      return "done";
 8    case "archived":
 9      return "done";
10    default:
11      throw new Error(`unknown status: ${status}`);
12  }
13}

For value-mapping, a plain object is often a better fit than switch:

1const labels = {
2  draft:     "In progress",
3  review:    "In progress",
4  published: "Done",
5  archived:  "Done",
6};
7const label = labels[status] ?? "Unknown";

Short-circuit evaluation

&& and || do not return booleans — they return one of their operands. That makes them useful for guards and defaults.

1// && — call only if user is truthy
2user && user.profile && user.profile.save();
3
4// || — fall back to a default (CAUTION: zero / empty string also fall back)
5const port = options.port || 3000;

The || default is the source of countless bugs: if options.port is 0 (a perfectly valid port for “any free port”), it is falsy and the default kicks in. Modern JavaScript has a better tool.

Nullish coalescing (??) — the right way to default

Added in ES2020. Returns the right-hand side only when the left is null or undefined — never when it is 0, "", or false.

1const port    = options.port    ?? 3000;     // keeps 0 if you meant it
2const message = options.message ?? "Ready";  // keeps "" if you meant it
3const dark    = options.dark    ?? false;    // keeps false if you meant it
Source||??
0falls backkeeps 0
""falls backkeeps ""
falsefalls backkeeps false
nullfalls backfalls back
undefinedfalls backfalls back
NaNfalls backkeeps NaN

Optional chaining (?.) — kill the guard pyramid

Also ES2020. Short-circuits to undefined the moment any link in the chain is null or undefined, so you never throw on a missing intermediate property.

1// Before — three nested guards
2const city = user && user.address && user.address.city;
3
4// After
5const city = user?.address?.city;
6
7// Works with function calls and dynamic keys
8const result    = api.search?.(query);     // call only if search exists
9const firstTag  = post?.tags?.[0];

Logical assignment operators

ES2021 added three assignment variants that are surprisingly handy:

1config.timeout ??= 5000;    // set only if currently null/undefined
2options.retries ||= 3;      // set only if currently falsy
3flags.enabled  &&= isReady; // set only if currently truthy

Each is shorthand for x = x ?? y, x = x || y, and x = x && y respectively.

A reusable decision: if, switch, ternary, or table?

SituationBest fit
Two distinct branches with side effectsif / else
Pick one of two valuesTernary
Three or more discrete cases on the same variableswitch or lookup
Mapping value → valueLookup object
Defaulting a nullable??
Defending against a potentially-missing property?.

Summary

  • Eight falsy values exist; everything else (including [] and {}) is truthy.
  • Use === and !==. Reserve == for the explicit x == null idiom — or, better, write the long form.
  • Replace || defaults with ?? unless you specifically want falsy values to be replaced.
  • Replace nested guards with ?..
  • For three or more discrete branches on one variable, prefer a lookup object over a switch.

The next post introduces JavaScript functions — declarations, expressions, parameters, and arrow functions.

Attempted Questions: 0 / 8