Skip to content
JavaScript js fundamentals 5 min read

Logical Operators

Logical operators let you combine conditions and make decisions: “this and that”, “this or that”, “not this”. In JavaScript they go further than simple true/false logic — && and || short-circuit and return one of their actual operands rather than a boolean, which powers a whole family of compact idioms for defaults and guards. The newer ?? (nullish coalescing) operator fills a long-standing gap left by ||. Understanding how truthiness drives all of this is essential to writing concise, bug-free code.

The three core operators

JavaScript has three logical operators. && (AND) and || (OR) are binary; ! (NOT) is unary.

OperatorNameExampleResult
&&Logical ANDtrue && falsefalse
||Logical ORtrue || falsetrue
!Logical NOT!truefalse

The ! operator always returns a real boolean: it coerces its operand to a boolean and flips it. Applying it twice (!!value) is a common trick to coerce any value to its boolean equivalent.

console.log(!0);          // true  (0 is falsy)
console.log(!"hello");    // false ("hello" is truthy)
console.log(!!"hello");   // true
console.log(!![]);        // true  (empty array is truthy)

Output:

true
false
true
true

The falsy values in JavaScript are exactly: false, 0, -0, 0n, "", null, undefined, and NaN. Everything else is truthy — including "0", "false", [], and {}.

Short-circuit evaluation

This is the part that trips up developers coming from other languages: && and || do not return true or false. They evaluate operands left to right and return one of the original operands, stopping as soon as the result is determined.

  • a && b evaluates a. If a is falsy, it returns a immediately (no need to check b). Otherwise it returns b.
  • a || b evaluates a. If a is truthy, it returns a immediately. Otherwise it returns b.
console.log(0 && "anything");   // 0      (stops at falsy left operand)
console.log("hi" && 42);        // 42     (left is truthy, returns right)
console.log("" || "fallback");  // fallback
console.log("name" || "guest"); // name   (stops at truthy left operand)

Output:

0
42
fallback
name

Because the right side is skipped entirely when the result is already known, you can use short-circuiting to guard expensive or unsafe operations:

const user = getUser();

// Only call .toUpperCase() if user and user.name exist
const display = user && user.name && user.name.toUpperCase();

// Run a function only when a condition holds
isLoggedIn && trackEvent("page_view");

The first line returns undefined (or whatever falsy value stopped it) instead of throwing, which is why short-circuiting was the classic way to avoid “Cannot read property of undefined” errors before optional chaining (?.) arrived.

Defaults with ||

The OR operator is frequently used to supply a fallback value: value || defaultValue returns value unless it is falsy.

function greet(name) {
  const who = name || "friend";
  return `Hello, ${who}!`;
}

console.log(greet("Ada"));  // Hello, Ada!
console.log(greet(""));     // Hello, friend!
console.log(greet());       // Hello, friend!

Output:

Hello, Ada!
Hello, friend!
Hello, friend!

This works well — until a legitimate value happens to be falsy. If 0, "", or false are valid inputs, || will wrongly discard them. That is exactly the problem ?? solves.

Nullish coalescing: ?? vs ||

The nullish coalescing operator (ES2020) returns its right-hand side only when the left-hand side is null or undefined — not for every falsy value. It treats 0, "", and false as real, usable values.

const count = 0;

console.log(count || 10);  // 10  (|| rejects the falsy 0)
console.log(count ?? 10);  // 0   (?? keeps 0, only nullish triggers fallback)

const config = { volume: 0, label: "" };
const volume = config.volume ?? 75;   // 0  (respected)
const label  = config.label  ?? "Untitled"; // "" (respected)

Output:

10
0
Left valueleft || rightleft ?? right
nullrightright
undefinedrightright
0right0
""right""
falserightfalse
"value""value""value"

Gotcha: You cannot mix ?? with && or || without parentheses — a ?? b || c is a syntax error. Write (a ?? b) || c to make precedence explicit. This restriction exists deliberately to prevent ambiguous code.

Practical idioms

Combining these operators yields several patterns you’ll see constantly:

// Guarded property access (pre-optional-chaining style)
const city = (user && user.address && user.address.city) || "Unknown";

// Default options object merged with caller overrides
function connect(options = {}) {
  const host = options.host ?? "localhost";
  const port = options.port ?? 5432;
  return `${host}:${port}`;
}

console.log(connect());                 // localhost:5432
console.log(connect({ port: 0 }));      // localhost:0  (?? keeps 0)

Output:

localhost:5432
localhost:0

For mutating assignments, the logical assignment operators (&&=, ||=, ??=) combine the operator with = and also short-circuit:

const settings = {};
settings.theme ??= "dark";   // assigns only if theme is null/undefined
settings.theme ??= "light";  // no-op, theme already set
console.log(settings.theme); // dark

Output:

dark

Best Practices

  • Reach for ?? when 0, "", or false are valid values; reserve || for true “any falsy means missing” cases.
  • Wrap ?? in parentheses whenever it sits next to && or || — the engine requires it and it documents intent.
  • Use !!value to explicitly coerce to a boolean rather than relying on implicit truthiness in return values.
  • Prefer optional chaining (?.) over long a && a.b && a.b.c guard chains for cleaner null-safe access.
  • Keep short-circuit side effects (cond && doThing()) for quick guards, but use a real if when the logic grows beyond one expression — readability wins.
  • Remember the falsy list; most truthiness bugs come from forgetting that [] and "0" are truthy.
Last updated June 1, 2026
Was this helpful?