JavaScriptLogical Operators

Logical Operators

The logical operators &&, || and ! look like they only deal with booleans, but they're actually general-purpose operand-returning operators that are happy with any values. Learning what they really return — not just whether they evaluate to "true-ish" — unlocks a lot of compact, idiomatic JavaScript.

The basics

JS
true && false;   // false
true || false;   // true
!true;           // false
!!"hi";          // true (double-bang: a tidy "to boolean")

Used with booleans, they behave exactly as you'd expect from any language. The interesting behaviour kicks in with non-boolean operands.

They return operands, not booleans

&& and || evaluate operands left to right and return one of the operands, not a true/false result.

JS
// && returns the first falsy operand, or the last one if all are truthy
"hello" && 42;        // 42
"" && 42;             // ""
0 && "anything";      // 0
{ a: 1 } && [1, 2];   // [1, 2]

// || returns the first truthy operand, or the last one if all are falsy
"hello" || "fallback";  // "hello"
""      || "fallback";  // "fallback"
0       || null;        // null   (both falsy → last one)
null    || 0 || "ok";   // "ok"

Think of && as "give me everything unless something fails" and || as "give me the first thing that's there".

Short-circuit evaluation

Once the result is decided, the remaining operands are not evaluated. This isn't an optimisation — it's a feature you can rely on.

JS
function expensive() {
  console.log("ran");
  return true;
}

false && expensive();   // doesn't run "ran"
true  || expensive();   // doesn't run "ran"
true  && expensive();   // logs "ran", returns true
ran

That property leads to some of the most common JavaScript idioms.

Idiomatic uses

Guarded property access (pre-?.)

JS
// Only read .length if items exists
const count = items && items.length;

// Optional chaining is the modern replacement
const count2 = items?.length;

Default values (pre-??)

JS
function greet(name) {
  const finalName = name || "stranger";
  console.log("Hello, " + finalName);
}

greet();          // "Hello, stranger"
greet("Ada");     // "Hello, Ada"
greet("");        // "Hello, stranger"  ← may not be intended!
greet(0);         // "Hello, stranger"  ← also probably wrong
`||` falls back on ALL falsy values
If `""`, `0` or `false` are legitimate inputs, `||` will treat them as missing. Reach for `??` (nullish coalescing) instead — it only falls back on `null` and `undefined`.
Nullish coalescing — a preview

?? was introduced in ES2020 specifically to fix the || gotcha.

JS
null      ?? "default";   // "default"
undefined ?? "default";   // "default"
0         ?? "default";   // 0
""        ?? "default";   // ""
false     ?? "default";   // false

Use ?? when you want "set a default unless the user explicitly provided a value" — including the value 0, the empty string, or false. Use || only when you genuinely want any falsy value to trigger the fallback.

Conditional rendering / side-effects

React-style libraries use && as a one-line conditional renderer because of the operand-returning semantics:

JS
// JSX:
// {isAdmin && <AdminPanel />}
//
// If isAdmin is true, the expression evaluates to <AdminPanel />.
// If false, it evaluates to false — which React renders as nothing.
Beware the 0 problem in JSX
`{items.length && <List />}` renders the literal `0` when `items` is empty, because `0` is what `&&` returns. Use `{items.length > 0 && <List />}` or `{Boolean(items.length) && <List />}` instead.
Forcing a boolean

Sometimes you want a real boolean — for serialisation, for an attribute, for a clean log. Boolean(x) and !!x both do it.

JS
!!"hello";         // true
!!0;                // false
!!null;             // false
!!{};               // true

Boolean("hello");   // true
Boolean(0);         // false

! already returns a boolean; the second ! flips it back. There is no behaviour difference — pick whichever your team finds clearer.

De Morgan's laws (in practice)

Two equivalences that show up when you refactor a condition:

  • !(a && b) is equivalent to !a || !b

  • !(a || b) is equivalent to !a && !b

JS
// Hard to read
if (!(isLoading || hasError)) {
  showContent();
}

// Easier
if (!isLoading && !hasError) {
  showContent();
}

// Or invert the early-exit
if (isLoading || hasError) return;
showContent();
Operator precedence reminder

&& binds tighter than ||. ! binds tighter than both.

JS
true || false && false;        // true
                                // parsed as: true || (false && false) → true || false → true

!false && true;                 // true
                                // parsed as: (!false) && true → true && true

When mixing && and || in one expression, parenthesise. Two seconds of extra typing saves your reader a minute of mental parsing.

Common mistakes

JS
// Using || when you mean ??
const port = config.port || 3000;
// If config.port is 0, you get 3000 instead of 0
const port2 = config.port ?? 3000;  // fixed

// Forgetting && short-circuits
const len = items && items.length;
// If items is 0, len is 0 — fine, but `items && something` evaluates to 0 not undefined.

// Mixing ?? with || or && without parens
// const x = a ?? b || c;          // SyntaxError — mixing ?? with || or && requires explicit grouping
const x = (a ?? b) || c;            // ok
The mental model
Read `a || b` as "a, or b if a is missing". Read `a && b` as "a, then b". Read `a ?? b` as "a, unless a is null/undefined". Once those three click, you'll write tighter, clearer code without thinking about it.