JavaScript Logical Short-Circuiting Complete Guide

Learn how JavaScript logical short-circuiting works with AND, OR, and nullish coalescing operators. Covers evaluation rules, default values, guard patterns, practical use cases, and common pitfalls.

JavaScriptbeginner
10 min read

Short-circuit evaluation is how JavaScript's logical operator (&&, ||, ??) skip evaluating the right-hand side when the left-hand side already determines the result. If the first operand of || is truthy, JavaScript returns it immediately without checking the second. If the first operand of && is falsy, JavaScript returns it without touching the second. This behavior is not just a performance detail. Developers use it intentionally for default values, guard checks, conditional execution, and safe property access.

This tutorial explains exactly how each operator short-circuits, what values they return (not always true or false), and the practical patterns that rely on this behavior.

How Short-Circuiting Works

Logical OR (||): Returns the First Truthy Value

The || operator evaluates left to right. It returns the first truthy value it encounters. If all values are falsy, it returns the last one:

javascriptjavascript
console.log("hello" || "world");   // "hello" (first is truthy, skip second)
console.log(0 || "fallback");      // "fallback" (0 is falsy, check second)
console.log("" || null || "end");  // "end" (first two are falsy)
console.log(0 || "" || null);      // null (all falsy, returns last)
Left valueRight valueResultRight evaluated?
TruthyAnythingLeftNo
FalsyTruthyRightYes
FalsyFalsyRightYes

Logical AND (&&): Returns the First Falsy Value

The && operator evaluates left to right. It returns the first falsy value it encounters. If all values are truthy, it returns the last one:

javascriptjavascript
console.log("hello" && "world");  // "world" (first is truthy, check second)
console.log(0 && "world");       // 0 (first is falsy, skip second)
console.log(1 && 2 && 3);        // 3 (all truthy, returns last)
console.log(1 && 0 && 3);        // 0 (first falsy found)
Left valueRight valueResultRight evaluated?
FalsyAnythingLeftNo
TruthyTruthyRightYes
TruthyFalsyRightYes
They [Return Value](/tutorials/programming-languages/javascript/what-is-a-function-in-javascript-beginner-guide)s, Not Booleans

Logical operators return one of the original values, not true or false. The expression "hello" || "world" returns the string "hello", not the boolean true. This is why short-circuiting works as a pattern for defaults and guards.

Nullish Coalescing (??): Returns Right Only for null/undefined

The ?? operator is a more precise version of ||. It returns the right side only when the left side is null or undefined, not for any falsy value:

javascriptjavascript
console.log(null ?? "default");      // "default"
console.log(undefined ?? "default"); // "default"
console.log(0 ?? "default");         // 0 (not null/undefined)
console.log("" ?? "default");        // "" (not null/undefined)
console.log(false ?? "default");     // false (not null/undefined)

|| vs ?? Comparison

Expression|| result?? result
0 || "fallback""fallback"0
"" || "fallback""fallback"""
false || "fallback""fallback"false
null || "fallback""fallback""fallback"
undefined || "fallback""fallback""fallback"
NaN || "fallback""fallback"NaN

Pattern 1: Default Values with ||

The most common use of || short-circuiting is providing a fallback when a value might be falsy:

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

Configuration with Multiple Fallbacks

javascriptjavascript
function getPort(config) {
  return config.port || process.env.PORT || 3000;
}
 
// Checks config.port first, then env variable, then falls back to 3000
javascriptjavascript
function getTheme(userPref, systemPref) {
  return userPref || systemPref || "light";
}
 
console.log(getTheme("dark", "light")); // "dark"
console.log(getTheme("", "light"));     // "light"
console.log(getTheme("", ""));          // "light"

Pattern 2: Safe Default Values with ??

When 0, "", or false are valid values, use ?? instead of ||:

javascriptjavascript
function createUser(options) {
  return {
    name: options.name ?? "Anonymous",
    age: options.age ?? 0,        // 0 is a valid age
    score: options.score ?? 0,    // 0 is a valid score
    active: options.active ?? true,
  };
}
 
// || would incorrectly replace these valid values:
const user1 = createUser({ name: "Bob", age: 0, score: 0, active: false });
console.log(user1);
// { name: "Bob", age: 0, score: 0, active: false }
 
// With ||, age would be 0||0 = 0 (works), but active would be false||true = true (wrong!)

Configuration Objects

javascriptjavascript
function initApp(config = {}) {
  const settings = {
    debug: config.debug ?? false,
    maxRetries: config.maxRetries ?? 3,
    timeout: config.timeout ?? 5000,
    prefix: config.prefix ?? "",
    verbose: config.verbose ?? false,
  };
 
  return settings;
}
 
// false, 0, and "" are preserved with ??
console.log(initApp({ debug: false, maxRetries: 0, prefix: "" }));
// { debug: false, maxRetries: 0, timeout: 5000, prefix: "", verbose: false }

Pattern 3: Guard Checks with &&

The && operator can conditionally execute the right side:

javascriptjavascript
const user = getUser();
 
// Only calls getName() if user is truthy
const name = user && user.getName();

Safe Property Access

javascriptjavascript
const response = fetchData();
 
// Chain of && checks each level before accessing deeper
const city = response && response.data && response.data.address && response.data.address.city;
 
// Modern alternative: optional chaining
const city = response?.data?.address?.city;

Conditional Function Calls

javascriptjavascript
const logger = getLogger();
 
// Only calls logger.info() if logger exists
logger && logger.info("Application started");
 
// Modern alternative
logger?.info("Application started");

Conditional Rendering (React-style)

javascriptjavascript
function UserProfile({ user, isAdmin }) {
  return `
    <div>
      <h1>${user.name}</h1>
      ${isAdmin && "<span>Admin Badge</span>"}
      ${user.bio && `<p>${user.bio}</p>`}
    </div>
  `;
}
Watch for Falsy Render Values

In React, {0 && <Component />} renders "0" on screen because && returns 0 (a falsy number), and React renders numbers. Use {count > 0 && <Component />} to ensure the left side evaluates to a boolean.

Pattern 4: Chaining Multiple Operators

You can combine ||, &&, and ?? for complex logic, but mixing ?? with || or && requires parentheses:

javascriptjavascript
// Valid: all same operator
const a = val1 || val2 || val3;
const b = val1 && val2 && val3;
 
// Valid: ?? with parentheses
const c = (val1 ?? val2) || val3;
const d = val1 || (val2 ?? val3);
 
// INVALID: mixing ?? with || or && without parentheses
// const e = val1 ?? val2 || val3; // SyntaxError!

Practical Chaining

javascriptjavascript
// Get the first available name source
function getDisplayName(user) {
  return user.displayName || user.fullName || user.email?.split("@")[0] || "Unknown";
}
 
// Validate before using
function processOrder(order) {
  const total = order && order.items && order.items.length > 0
    ? calculateTotal(order.items)
    : 0;
 
  return total;
}

Pattern 5: Assignment with Short-Circuit Operators

Logical OR Assignment (||=)

javascriptjavascript
let title = "";
title ||= "Untitled"; // assigns only if title is falsy
console.log(title); // "Untitled"
 
let name = "Alice";
name ||= "Default"; // does NOT assign because name is truthy
console.log(name); // "Alice"

Logical AND Assignment (&&=)

javascriptjavascript
let user = { name: "Alice", score: 10 };
user.score &&= user.score * 2; // assigns only if score is truthy
console.log(user.score); // 20
 
user.score = 0;
user.score &&= user.score * 2; // does NOT assign because 0 is falsy
console.log(user.score); // 0

Nullish Coalescing Assignment (??=)

javascriptjavascript
let config = { timeout: 0, retries: null };
 
config.timeout ??= 5000; // 0 is not null/undefined, keeps 0
config.retries ??= 3;    // null IS nullish, assigns 3
 
console.log(config); // { timeout: 0, retries: 3 }

Side Effects and Short-Circuiting

Short-circuiting prevents execution of the right-hand side. This matters when the right side has side effects:

javascriptjavascript
let counter = 0;
 
function increment() {
  counter++;
  return counter;
}
 
// increment() is NOT called because true short-circuits ||
true || increment();
console.log(counter); // 0
 
// increment() IS called because false doesn't short-circuit ||
false || increment();
console.log(counter); // 1
 
// increment() is NOT called because false short-circuits &&
false && increment();
console.log(counter); // 1
 
// increment() IS called because true doesn't short-circuit &&
true && increment();
console.log(counter); // 2

Practical Implication: API Calls

javascriptjavascript
const cachedData = getFromCache(key);
 
// fetchFromAPI is only called if cache miss (cachedData is null/undefined)
const data = cachedData ?? fetchFromAPI(key);
javascriptjavascript
// Log only when debug mode is enabled
isDebugMode && console.log("Debug:", someExpensiveCalculation());

Operator Precedence

&& has higher precedence than ||, which has higher precedence than ??:

javascriptjavascript
// && binds tighter than ||
const result = a || b && c;
// Equivalent to: a || (b && c)
// NOT: (a || b) && c
 
console.log(false || true && false); // false
// Evaluates as: false || (true && false) => false || false => false
OperatorPrecedenceEvaluates
! (NOT)HighestRight to left
&& (AND)MediumLeft to right
|| (OR)LowerLeft to right
?? (Nullish)LowestLeft to right
javascriptjavascript
// When in doubt, use parentheses for clarity
const canEdit = (isAdmin || isOwner) && !isLocked;
const display = (userName ?? "Guest") || "Unknown";

Common Mistakes

Mistake 1: Using || When 0 or "" Are Valid

javascriptjavascript
// Bug: port 0 would be replaced
const port = userPort || 3000;
 
// Fix: use ?? to preserve 0
const port = userPort ?? 3000;

Mistake 2: Side Effects in Short-Circuited Expressions

javascriptjavascript
// Bug: cleanup() never runs if condition is false
condition && (doWork(), cleanup());
 
// Fix: use an if statement for multi-step logic
if (condition) {
  doWork();
  cleanup();
}

Mistake 3: Forgetting && Returns Values, Not Booleans

javascriptjavascript
// Bug: assigns 0 instead of false
const isValid = 0 && checkValidity();
console.log(isValid);        // 0 (not false)
console.log(typeof isValid); // "number"
 
// Fix: use Boolean() or !! if you need a boolean
const isValid = Boolean(0 && checkValidity());

Mistake 4: Mixing ?? with || Without Parentheses

javascriptjavascript
// SyntaxError: cannot mix ?? with || or && without parentheses
// const val = a ?? b || c;
 
// Fix: add parentheses to clarify intent
const val = (a ?? b) || c;
// or
const val = a ?? (b || c);

Short-Circuiting vs If Statements

Short-circuit expressions are concise but not always clearer. Use if statements when:

Use short-circuitingUse if statement
Simple default valueMultiple statements to execute
One-line guard checkComplex branching logic
Inline conditional in templateSide effects that need clarity
Chaining 2-3 fallbacksDeeply nested conditions
javascriptjavascript
// Short-circuit: clean for simple defaults
const name = input || "default";
 
// If: better for complex logic
if (user && user.isActive) {
  sendNotification(user);
  logActivity(user.id);
  updateLastSeen(user.id);
}
Rune AI

Rune AI

Key Insights

  • || returns the first truthy value: use for fallback defaults when any falsy value should trigger the fallback
  • && returns the first falsy value: use for guard checks and conditional execution
  • ?? returns right only for null/undefined: use when 0, "", or false are valid and should be preserved
  • They return values, not booleans: "hello" || "world" returns "hello", not true
  • Mixing ?? with ||/&& requires parentheses: JavaScript throws a SyntaxError without them
RunePowered by Rune AI

Frequently Asked Questions

Does short-circuiting improve performance?

Yes, but usually in a negligible way. When the right-hand side involves an expensive operation (API call, heavy computation, DOM query), short-circuiting avoids that cost entirely when the left side determines the result. For simple value comparisons, the performance difference is unmeasurable.

Can I use short-circuiting with async/await?

Yes, but be cautious. `const data = cache || await fetchData()` works: if `cache` is truthy, `fetchData()` is never called. However, `await` only applies to the right side. The expression does not become async just because `await` appears on one side.

Why does JavaScript return values instead of booleans from logical operators?

This is by language design. Returning the actual values (not `true`/`false`) enables patterns like default values (`name || "Guest"`) and guard checks (`user && user.name`). Many languages (Python, Ruby) work the same way.

What is the difference between && and optional chaining (?.)?

Both prevent errors when accessing properties of null/undefined. `user && user.name` returns `user` (the falsy value) if user is falsy. `user?.name` returns `undefined` if user is null/undefined. Optional chaining is more specific: it only checks for null/undefined, while `&&` stops on any falsy value including `0`, `""`, and `false`.

Can I chain more than two ?? operators?

Yes. `a ?? b ?? c ?? d` evaluates left to right and returns the first value that is not null or undefined. If all are nullish, it returns the last value (`d`). This is useful for fallback chains with configuration values.

Conclusion

Short-circuit evaluation in JavaScript is both a performance mechanism and a coding pattern. The || operator returns the first truthy value (or the last value if all are falsy). The && operator returns the first falsy value (or the last value if all are truthy). The ?? operator returns the right side only when the left is null or undefined. These operators return actual values, not booleans, which enables patterns like default values with ||, guard checks with &&, and safe defaults with ??. Use ?? over || when 0, "", or false are legitimate values that should not be replaced.