skip to content

In JavaScript, what value do the `||` and `&&` operators produce — a boolean, or one of their operands? Walk through `'' || 'default'` and `0 && compute()`.

level: middleimportance: must knowfreq 70%

answer

  1. the test is boolean, the result is not
  2. one of the two operands comes back verbatim
  3. first truthy for one, first falsy for the other
  4. the losing side is never evaluated
  5. wrap in Boolean() when a real boolean is the contract

basics

~20 s

Both return one of their operands unchanged, never a coerced boolean. || returns the left operand if it is truthy, otherwise the right one; && returns the left operand if it is falsy, otherwise the right one. The unselected side is never evaluated.

solid answer

~50 s

`||` and `&&` are selection operators, not boolean operators. Each evaluates its left operand, applies ToBoolean to *test* it, and then returns one of the two operands **as-is** — the test result is thrown away. So `'' || 'default'` is the string `'default'` (the empty string is falsy, so the right operand is returned), and `'a' || 'b'` is `'a'`, not `true`. `&&` mirrors it: `0 && compute()` short-circuits, returns the number `0`, and `compute()` is never called at all; `'a' && 42` returns `42`. Two consequences matter in practice. First, the result can be any type, so `x && x.length` yields `0`, `undefined` or a number depending on `x`. Second, short-circuiting means the right operand's side effects are conditional — which is exactly why `obj && obj.load()` is safe. If you need an actual boolean, wrap the whole thing: `Boolean(a || b)` or `!!(a || b)`.

code

javascript · 11 lines
javascript
let calls = 0;
const compute = () => { calls++; return 99; };

console.log('' || 'default');   // 'default'  (right operand returned)
console.log('a' || 'b');        // 'a'        (left operand, not true)
console.log(0 && compute());    // 0          (left operand, not false)
console.log('a' && compute());  // 99
console.log(calls);             // 1 — the short-circuited call never ran

console.log(typeof ('a' || 'b'));        // 'string'
console.log(typeof Boolean('a' || 'b')); // 'boolean'

go deeper

for a junior

Know that these operators hand back one of the two values you gave them rather than true or false, and be able to say what '' || 'default' evaluates to.

for a middle

Explain the four-step evaluation rule, that ToBoolean is applied only to the left operand as a test, and that the unselected operand is never evaluated at all.

for a senior

Show that you police return contracts: an expression like list && list.length escaping a function as its return value is a type leak you would catch in review and wrap in Boolean.

for a principal

Own the readability tradeoff — short-circuit expressions used as statements hide conditional side effects from reviewers, so set a house rule on where the operand-returning idiom is welcome and where an explicit if belongs.

## They are not boolean operators The name "logical operators" is misleading. In many languages `||` and `&&` take booleans and produce booleans. In JavaScript they take *any two values* and produce **one of those two values**, unchanged. The boolean only appears internally, as the test. The evaluation rule for `a || b`: 1. Evaluate `a`. 2. Apply ToBoolean to the result. 3. If it is `true`, return `a`'s value — `b` is never evaluated. 4. Otherwise evaluate `b` and return its value, whatever it is. `a && b` is the exact mirror: 1. Evaluate `a`. 2. Apply ToBoolean to the result. 3. If it is `false`, return `a`'s value — `b` is never evaluated. 4. Otherwise evaluate `b` and return its value. A useful one-liner: `||` returns the **first truthy operand, or the last operand**; `&&` returns the **first falsy operand, or the last operand**. Chains follow directly — `a || b || c` yields the first truthy of the three, falling back to `c`. ```js '' || 'default'; // 'default' — '' is falsy, so the right operand is returned 'a' || 'b'; // 'a' — not true 0 && compute(); // 0 — compute() is never called 'a' && 42; // 42 null && anything; // null — the falsy operand itself comes back ``` Notice that `0 && compute()` returns the number `0`, not `false`. The falsy operand is handed back **verbatim**, preserving its type. This is the detail interviewers are actually probing. ## Short-circuiting is a control-flow feature Because the unselected operand is never evaluated, `&&` and `||` are the only operators besides `?:` and `??` that can skip work. This underpins three everyday idioms: ```js // guarded call — load() runs only if obj is truthy obj && obj.load(); // cheap-first ordering — the expensive check runs only when needed if (cache.has(key) || expensiveLookup(key)) { /* … */ } // fallback value const name = input || 'anonymous'; ``` It also means an operand with side effects is conditional, which cuts both ways: `count++ && log()` increments always, while `flag && count++` increments only sometimes. When reviewing, treat any function call on the right of `&&`/`||` as "may not happen". ## Returning an operand, not a boolean, has consequences Because the result carries its original type, a value can leak somewhere you expected a boolean: ```js function hasItems(list) { return list && list.length; // returns undefined, 0, or a number — not a boolean } ``` The caller writing `if (hasItems(x))` still works, because they re-apply ToBoolean — but the function's contract is a lie, `hasItems(x) === true` fails, and serialising the result puts a number in your JSON. If a boolean is the contract, produce one: `return Boolean(list && list.length)` or `return !!list?.length`. The contrast with `!` is worth stating explicitly: `!` always yields a genuine boolean, so `!!x` is the canonical coercion. `x || y` is not a coercion at all. ## Precedence and grouping `&&` binds tighter than `||`, so `a || b && c` parses as `a || (b && c)`. Both bind looser than comparison operators, which is why `a === 1 || a === 2` works without parentheses. Mixing either operator with `??` in the same expression without parentheses is a **SyntaxError** — the language forces you to be explicit rather than guess a precedence. ## Assignment forms `||=` and `&&=` apply the same test and, importantly, **short-circuit the assignment itself**: `a ||= b` assigns only when `a` is falsy, so a setter or a proxy trap does not fire on the skipped path. That is a different behaviour from `a = a || b`, which always writes. ## What a strong answer sounds like Say the mechanism, not the folklore: the operator tests the left operand with ToBoolean and returns one of the operands unchanged, so the result's type is whatever was selected; the other operand is not evaluated; and if you need a real boolean you wrap the expression in `Boolean(...)` or `!!`. Then give one concrete pair — `0 || 'x'` is `'x'`, `0 && f()` is `0` and `f` never runs — because that pair demonstrates both halves at once.

  • How would you turn `list && list.length` into something a caller can safely compare with `=== true`?
    Wrap the whole expression so ToBoolean runs once at the end: `Boolean(list && list.length)` or `!!(list && list.length)`. Both produce a real boolean regardless of which operand was selected. Returning the raw expression leaks `undefined` or `0` into callers, breaks strict comparison, and puts a number into anything that serialises the result.
  • Does `a ||= b` behave identically to `a = a || b`?
    Not quite. Both assign only when `a` is falsy in terms of the resulting value, but `a = a || b` performs the assignment unconditionally — it writes `a` back to itself on the truthy path. `||=` short-circuits the write itself, so a setter, a proxy `set` trap, or a reactive property is not triggered when the left side is already truthy.
  • Why does `a ?? b || c` fail to parse in JavaScript?
    It is a deliberate SyntaxError. `??` and the logical operators have confusable precedence and different tests — one checks nullishness, the other truthiness — so the grammar forbids mixing them unparenthesised rather than silently picking an order. Write `(a ?? b) || c` or `a ?? (b || c)` to state which you mean.

saying these in an interview costs you the question

  • Says `'a' || 'b'` evaluates to true
  • Thinks `0 && f()` returns false rather than 0
  • Believes both operands are always evaluated
  • Claims `||` coerces its result to a boolean
  • Confuses `a ||= b` with an unconditional assignment

context