Episode 1 — Fundamentals / 1.18 — Operators and Type System

1.18.c — Logical Operators

In one sentence: JavaScript's logical operators (&&, ||, !, ??) do not simply return true/false — they evaluate using short-circuit logic and return the actual operand value that determined the result, which enables powerful patterns for defaults, guards, and conditional assignment.

Navigation: ← 1.18.b — Comparison Operators · 1.18.d — Assignment Operators →


1. Logical AND &&

&& returns the first falsy operand it encounters, or the last operand if all are truthy. It does not always return a boolean.

console.log(true && "hello");     // "hello"  (both truthy → returns last)
console.log(false && "hello");    // false    (first is falsy → returns it)
console.log("a" && "b" && "c");   // "c"      (all truthy → last value)
console.log("a" && 0 && "c");     // 0        (first falsy encountered)
console.log("a" && "" && "c");    // ""       (empty string is falsy)
console.log(1 && 2 && 3);         // 3
console.log(1 && 0 && 3);         // 0

Short-circuit evaluation

&& stops evaluating as soon as it finds a falsy value. The right operand is never executed if the left is falsy.

let count = 0;
false && count++;           // count stays 0 (right side never runs)
console.log(count);         // 0

true && count++;            // count becomes 1
console.log(count);         // 1

Guard pattern (pre-optional-chaining)

// Before optional chaining (?.)
const street = user && user.address && user.address.street;

// Modern equivalent
const street2 = user?.address?.street;

Conditional rendering (React pattern)

// In JSX: only renders <Welcome /> if isLoggedIn is truthy
// {isLoggedIn && <Welcome />}

const isLoggedIn = true;
const message = isLoggedIn && "Welcome back!";
console.log(message);  // "Welcome back!"

2. Logical OR ||

|| returns the first truthy operand it encounters, or the last operand if all are falsy.

console.log(false || "hello");      // "hello"  (first truthy)
console.log("" || "default");       // "default"
console.log(0 || 42);               // 42
console.log(null || undefined);     // undefined (both falsy → last)
console.log("a" || "b");            // "a"      (first is truthy → returns it)
console.log(false || 0 || "" || "found"); // "found"
console.log(false || 0 || "" || null);    // null (all falsy → last)

Short-circuit evaluation

|| stops evaluating as soon as it finds a truthy value.

let init = false;
true || (init = true);    // init stays false (right side never runs)
console.log(init);         // false

false || (init = true);   // init becomes true
console.log(init);         // true

Default value pattern

function greet(name) {
  const displayName = name || "Guest";
  return `Hello, ${displayName}!`;
}
console.log(greet("Alice"));     // "Hello, Alice!"
console.log(greet(""));          // "Hello, Guest!"  (problem! "" is falsy)
console.log(greet(0));           // "Hello, Guest!"  (problem! 0 is falsy)
console.log(greet(undefined));   // "Hello, Guest!"  (intended)

Problem: || treats 0, "", and false as falsy — if those are valid values, || will incorrectly replace them. This is why ?? was introduced.


3. Logical NOT !

! converts the operand to a boolean and inverts it.

console.log(!true);        // false
console.log(!false);       // true
console.log(!"hello");     // false  ("hello" is truthy)
console.log(!"");          // true   ("" is falsy)
console.log(!0);           // true
console.log(!null);        // true
console.log(!undefined);   // true
console.log(!NaN);         // true

Double negation !! — explicit boolean conversion

!!value is a common idiom to convert any value to its boolean equivalent:

console.log(!!"hello");     // true
console.log(!!"");          // false
console.log(!!0);           // false
console.log(!!1);           // true
console.log(!!null);        // false
console.log(!!undefined);   // false
console.log(!!{});          // true   (objects are truthy)
console.log(!![]);          // true   (arrays are truthy)

Equivalent to Boolean(value), but shorter. Some style guides prefer Boolean() for readability.

// Both do the same thing
const hasName = !!user.name;
const hasName2 = Boolean(user.name);

4. Nullish coalescing ?? (ES2020)

?? returns the right operand only if the left is null or undefined. It does NOT treat 0, "", or false as "missing."

console.log(null ?? "default");       // "default"
console.log(undefined ?? "default");  // "default"
console.log(0 ?? "default");          // 0         (0 is NOT null/undefined)
console.log("" ?? "default");         // ""        ("" is NOT null/undefined)
console.log(false ?? "default");      // false     (false is NOT null/undefined)

?? vs || — the critical difference

Expression|| result?? result
0 || 42420
"" || "default""default"""
false || truetruefalse
null || "fallback""fallback""fallback"
undefined || "fallback""fallback""fallback"
// Real-world: user sets volume to 0 (valid!)
function setVolume(level) {
  // BUG with ||
  const vol1 = level || 50;   // 0 → 50 (wrong! user wanted 0)

  // CORRECT with ??
  const vol2 = level ?? 50;   // 0 → 0 (correct!)

  return vol2;
}
console.log(setVolume(0));      // 0  (correct)
console.log(setVolume(null));   // 50 (correct)
console.log(setVolume());       // 50 (correct — undefined)

Cannot mix ?? with && or || without parentheses

// SyntaxError: cannot mix ?? with && or || without parentheses
// null || undefined ?? "default"  // ERROR

// Must use parentheses
(null || undefined) ?? "default";   // "default"
null || (undefined ?? "default");   // "default"

5. Operator precedence: NOT > AND > OR

// NOT has highest precedence
console.log(!true || false);         // false || false → false
console.log(!(true || false));       // !true → false

// AND binds tighter than OR
console.log(true || false && false);
// Evaluates as: true || (false && false) → true || false → true

console.log((true || false) && false);
// (true) && false → false

Full precedence order for logical operators

Precedence (high → low)Operator
1! (NOT)
2&& (AND)
3`
4?? (Nullish coalescing)
// Complex example
const result = !false && true || false;
// Step 1: !false → true
// Step 2: true && true → true
// Step 3: true || false → true
console.log(result);  // true

Best practice: Use parentheses whenever you combine more than two logical operators. Do not rely on precedence for readability.


6. Using logical operators for conditional assignment

&& for conditional execution

const isAdmin = true;
isAdmin && console.log("Admin panel loaded");  // logs it

const isGuest = false;
isGuest && console.log("Guest mode");          // does NOT log

|| for fallback values

const config = {
  timeout: userTimeout || 3000,
  retries: userRetries || 3,
  theme:   userTheme   || "light",
};

?? for safer fallbacks

const settings = {
  volume:   userVolume   ?? 50,        // 0 is valid
  nickname: userNickname ?? "Player",  // "" is valid
  darkMode: userDarkMode ?? false,     // false is valid
};

7. Guard patterns and default values in depth

Nested guard (before optional chaining)

// Old pattern
const zipCode = user && user.address && user.address.zip;

// Modern equivalent
const zipCode2 = user?.address?.zip;

// With default
const zipCode3 = user?.address?.zip ?? "N/A";

Function parameter defaults vs ||/??

// ES6 default parameters only kick in for undefined (not null)
function createUser(name = "Anonymous") {
  return { name };
}
console.log(createUser());           // { name: "Anonymous" }
console.log(createUser(undefined));  // { name: "Anonymous" }
console.log(createUser(null));       // { name: null }  (default NOT used!)
console.log(createUser(""));         // { name: "" }

// Use ?? inside the function for null handling too
function createUser2(name) {
  return { name: name ?? "Anonymous" };
}
console.log(createUser2(null));  // { name: "Anonymous" }

Chaining ?? with optional chaining

const config = {
  api: {
    // timeout might be 0 (valid)
  }
};

const timeout = config?.api?.timeout ?? 5000;
console.log(timeout);  // 5000 (undefined → default)

8. Real-world patterns in React/JS codebases

Conditional rendering

// React JSX pattern
// {items.length > 0 && <ItemList items={items} />}
// {error && <ErrorMessage error={error} />}
// {isLoading ? <Spinner /> : <Content />}

Feature flags

const features = {
  darkMode: true,
  betaFeature: false,
};

features.darkMode && enableDarkMode();
features.betaFeature && showBetaBanner();

Configuration merging

function createConfig(userOptions) {
  return {
    host:    userOptions?.host    ?? "localhost",
    port:    userOptions?.port    ?? 3000,
    debug:   userOptions?.debug   ?? false,
    timeout: userOptions?.timeout ?? 5000,
  };
}

console.log(createConfig({ port: 8080, debug: true }));
// { host: "localhost", port: 8080, debug: true, timeout: 5000 }

console.log(createConfig({ port: 0 }));
// { host: "localhost", port: 0, debug: false, timeout: 5000 }
// port: 0 is preserved! (|| would have replaced it with 3000)

Short-circuit for lazy evaluation

// Expensive function only called if needed
const cachedResult = cache.get(key) ?? expensiveComputation(key);

Key takeaways

  1. && returns the first falsy value or the last value; || returns the first truthy value or the last value.
  2. Both && and || use short-circuit evaluation — the right side may never execute.
  3. ! converts to boolean and inverts; !! is a common boolean-casting idiom.
  4. ?? only falls back for null/undefined — it preserves 0, "", and false as valid values.
  5. Precedence: ! > && > || > ?? — use parentheses for clarity.
  6. || for defaults has a flaw: it replaces all falsy values. Prefer ?? when 0, "", or false are valid.
  7. You cannot mix ?? with &&/|| without parentheses — this is a syntax error by design.

Explain-It Challenge

Explain without notes:

  1. What does 0 || "fallback" return? What about 0 ?? "fallback"? Why are they different?
  2. In a && b, under what conditions is b never evaluated?
  3. What is the double negation !! pattern, and when would you use it?
  4. Write a one-liner that safely accesses user.address.city with a fallback of "Unknown".
  5. Why can you not write a || b ?? c without parentheses in JavaScript?

Navigation: ← 1.18.b — Comparison Operators · 1.18.d — Assignment Operators →