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
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.
// && 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.
function expensive() {
console.log("ran");
return true;
}
false && expensive(); // doesn't run "ran"
true || expensive(); // doesn't run "ran"
true && expensive(); // logs "ran", returns trueran
That property leads to some of the most common JavaScript idioms.
Idiomatic uses
Guarded property access (pre-?.)
// Only read .length if items exists const count = items && items.length; // Optional chaining is the modern replacement const count2 = items?.length;
Default values (pre-??)
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
Nullish coalescing — a preview
?? was introduced in ES2020 specifically to fix the || gotcha.
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:
// JSX:
// {isAdmin && <AdminPanel />}
//
// If isAdmin is true, the expression evaluates to <AdminPanel />.
// If false, it evaluates to false — which React renders as nothing.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.
!!"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
// 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.
true || false && false; // true
// parsed as: true || (false && false) → true || false → true
!false && true; // true
// parsed as: (!false) && true → true && trueWhen mixing && and || in one expression, parenthesise. Two seconds of extra typing saves your reader a minute of mental parsing.
Common mistakes
// 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