Conditionals & Operators (??, ?.)

Reviewed & published by Brayan K

Every interesting program makes decisions: show this if logged in, charge that if the cart isn't empty, fall back to a default when data is missing. Modern JavaScript gives you elegant tools for all of it — the ternary, nullish coalescing ??, and optional chaining ?..

Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

These operators replace whole blocks of defensive if statements with a single clear line.

What You'll Learn in This Lesson

🚦 Real-World Analogy: Conditionals are the traffic lights of your code:

🧠 if / else if / else

The workhorse of decision-making. Conditions are checked top to bottom; the first truthy one wins and the rest are skipped.

⚖️ Comparison & Logical Operators

Comparisons produce booleans; logical operators combine them. Always prefer === over == (you'll see exactly why in the Type Coercion lesson).

❓ The Ternary Operator

condition ? valueIfTrue : valueIfFalse picks between two values in a single expression. It's ideal for assignments and inline labels:

🛟 Nullish Coalescing ?? — Smarter Defaults

?? returns the right side only when the left is null or undefined. This is crucial when 0, "", or false are legitimate values that || would wrongly discard:

(The blank line is the empty string printed by name ?? "Guest".)

🔒 Optional Chaining ?. — Crash-Proof Access

Reaching into nested data that might be missing used to need a wall of && guards. ?. does it in one operator, returning undefined the moment something is nullish:

🔁 switch & Short-Circuiting

switch compares one value against many constant cases. && and || short-circuit, returning an operand rather than a strict boolean — handy for conditional actions:

// Ternary for a label
const score = 72;
console.log(score >= 60 ? "Pass" : "Fail");

// ?? vs || with a real config
const settings = { volume: 0, theme: null };
console.log("Volume (||):", settings.volume || 50); // 50  (wrong!)
console.log("Volume (??):", settings.volume ?? 50); // 0   (right)
console.log("Theme:", settings.theme ?? "light");

// Optional chaining on nested API-ish data
const response = {
  user: { profile: { handle: "@ada" } },
};
console.log(response.user?.profile?.handle ?? "anonymous");
console.log(response.user?.billing?.plan ?? "free tier");

// switch for a status code
function statusText(code) {
  switch (code) {
    case 200: return "OK";
    case 404: return "Not Found";
    case 500: return "Server Error";
    default:  return "Unknown";
  }
}
console.log(statusText(404));

// Try it: write a function that greets by time of day using a ternary chain!
// 🎯 YOUR TURN — replace each ___ using the hint beside it.

const order = { total: 82, member: true, country: "UK" };

let band;
if (order.total >= 100) {
  band = "free delivery";
// 1) A second test, run only when the first one failed.
} ___ (order.total >= 50) {              // 👉 replace ___ with else if
  band = "reduced delivery";
} else {
  band = "standard delivery";
}
console.log("Band:", band);

// 2) Pick one of two values inline: condition ? whenTrue : whenFalse.
const rate = order.member ___ 0.9 : 1.0;  // 👉 replace ___ with ?
console.log("Rate:", rate);

// 3) One value against several exact matches.
___ (order.country) {                     // 👉 replace ___ with switch
  case "UK":
    console.log("Ships in 2 days");
    // 4) Without this, execution falls straight into the next case.
    ___;                                  // 👉 replace ___ with break
  case "IE":
    console.log("Ships in 4 days");
    break;
  default:
    console.log("Ships when it ships");
}

console.log("Discounted:", (order.total * rate).toFixed(2));

// 5) ?? uses the fallback only for null/undefined — || would also
//    replace 0 and "", which is usually a bug.
const note = order.note ___ "no note left";   // 👉 replace ___ with ??
console.log("Note:", note);

console.log("Truthy check:", Boolean(order.total), Boolean(0), Boolean(""));

// ✅ Expected output:
// Band: reduced delivery
// Rate: 0.9
// Ships in 2 days
// Discounted: 73.80
// Note: no note left
// Truthy check: true false false

🧩 Reorder Challenge

These lines should safely read a user's city and fall back to "Unknown", then print a greeting. They are scrambled — reorder them.

B → A → D → C → E

Why: the object (B) must exist before you read from it (A). Because user has no address, optional chaining returns undefined and ?? supplies "Unknown" — without ?. this line would throw. isLocal (D) depends on city, and the ternary (C) depends on isLocal, so the final log (E) reads: Welcome, Mia from Unknown.

🧠 Quick Recall

0 then fallback. ?? only replaces null/undefined, so 0 ?? "fallback" keeps 0. || replaces any falsy value, and 0 is falsy, so it returns "fallback".

2. What is the result?

undefined — obj.x is undefined, so optional chaining short-circuits the whole expression to undefined instead of throwing a TypeError when trying to read .y.

3. What does this log?

done — && returns the first falsy operand or, if all are truthy, the last one. All three values are truthy, so it returns the final value "done".

⚠️ Common Mistakes to Avoid

Lesson Complete — Conditionals & Operators!

Practice quiz

In an if / else if / else chain, which block runs?

  • The first block whose condition is truthy
  • Every block whose condition is truthy
  • The last block whose condition is truthy
  • Only the else block

Answer: The first block whose condition is truthy. Conditions are checked top to bottom; the first truthy one runs and the rest are skipped.

What does console.log(0 ?? 10) output?

  • 10
  • 0
  • undefined
  • null

Answer: 0. ?? only falls back when the left side is null or undefined; 0 is neither, so it is kept.

What does console.log(0 || 10) output?

  • 0
  • 10
  • true
  • false

Answer: 10. || falls back on ANY falsy value, and 0 is falsy, so it returns 10.

The ternary operator follows which syntax?

  • condition ? valueIfTrue : valueIfFalse
  • condition : valueIfTrue ? valueIfFalse
  • if condition then a else b
  • condition ?? a : b

Answer: condition ? valueIfTrue : valueIfFalse. A ternary is written condition ? valueIfTrue : valueIfFalse and picks one value in a single expression.

Given const user = { name: 'Ada' }; what does console.log(user.contact?.email) output?

  • TypeError
  • null
  • undefined
  • ''

Answer: undefined. Optional chaining short-circuits to undefined the moment user.contact is undefined, instead of throwing.

What does console.log('hi' && 42 && 'done') log?

  • true
  • hi
  • 42
  • done

Answer: done. && returns the first falsy operand, or the last value if all are truthy; here all are truthy so it returns 'done'.

What does console.log(0 && 'skipped') output?

  • 0
  • skipped
  • false
  • undefined

Answer: 0. && returns the first falsy operand, and 0 is falsy, so it short-circuits and returns 0.

Why prefer ?? over || when 0 or '' are valid values?

  • ?? is faster than ||
  • || replaces any falsy value, wrongly discarding a valid 0 or ''
  • ?? only works on numbers
  • || throws on null

Answer: || replaces any falsy value, wrongly discarding a valid 0 or ''. || falls back on every falsy value, so a legitimate 0 or '' gets discarded; ?? only falls back on null/undefined.

Given const data = { items: ['a', 'b'] }; what does console.log(data.items?.[0]) output?

  • undefined
  • a
  • ['a', 'b']
  • b

Answer: a. Optional chaining also works for array access; items exists, so ?.[0] reads the first element 'a'.

In a switch statement, what happens if you forget break (or return) in a case?

  • A SyntaxError is thrown
  • Execution falls through to the next case
  • Only the default runs
  • The switch is skipped entirely

Answer: Execution falls through to the next case. Without break or return, execution falls through and runs the following case(s) too.

Continue this course

Frequently asked questions

What's the difference between ?? and ||?

Both provide a fallback, but they trigger on different inputs. || falls back when the left side is any falsy value (0, '', false, null, undefined, NaN). ?? (nullish coalescing) falls back ONLY when the left side is null or undefined. Use ?? when 0 or '' are valid values you want to keep — like a quantity of 0 or an empty search string.

What does the ?. optional chaining operator do?

?. safely reads a property that might not exist. user?.address?.city returns undefined instead of throwing 'Cannot read properties of undefined' if user or address is missing. It short-circuits: the moment something is null or undefined, the whole expression becomes undefined. It also works for method calls (obj.method?.()) and array access (arr?.[0]).

When should I use a ternary instead of if/else?

Use a ternary (condition ? a : b) when you're choosing between two values in an expression — assigning a variable or returning from a function. Use if/else when you're running statements or have multiple branches. Avoid nesting ternaries deeply; once you need a second condition, an if/else or a lookup object usually reads better.

How do && and || short-circuit?

&& stops and returns the first falsy value (or the last value if all are truthy). || stops and returns the first truthy value (or the last if all are falsy). This means they don't always return true/false — they return one of the operands. It's why isLoggedIn && showDashboard() only calls the function when isLoggedIn is truthy.

Is switch better than a long if/else chain?

switch is cleaner when you compare ONE value against many constant cases, like a status code or a command name. Remember to break each case or execution 'falls through' to the next. For range checks or complex conditions, if/else if is more flexible. A lookup object can replace many switches entirely.