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.
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:
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 value | Right value | Result | Right evaluated? |
|---|---|---|---|
| Truthy | Anything | Left | No |
| Falsy | Truthy | Right | Yes |
| Falsy | Falsy | Right | Yes |
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:
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 value | Right value | Result | Right evaluated? |
|---|---|---|---|
| Falsy | Anything | Left | No |
| Truthy | Truthy | Right | Yes |
| Truthy | Falsy | Right | Yes |
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:
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:
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
function getPort(config) {
return config.port || process.env.PORT || 3000;
}
// Checks config.port first, then env variable, then falls back to 3000function 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 ||:
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
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:
const user = getUser();
// Only calls getName() if user is truthy
const name = user && user.getName();Safe Property Access
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
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)
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:
// 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
// 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 (||=)
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 (&&=)
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); // 0Nullish Coalescing Assignment (??=)
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:
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); // 2Practical Implication: API Calls
const cachedData = getFromCache(key);
// fetchFromAPI is only called if cache miss (cachedData is null/undefined)
const data = cachedData ?? fetchFromAPI(key);// Log only when debug mode is enabled
isDebugMode && console.log("Debug:", someExpensiveCalculation());Operator Precedence
&& has higher precedence than ||, which has higher precedence than ??:
// && 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| Operator | Precedence | Evaluates |
|---|---|---|
! (NOT) | Highest | Right to left |
&& (AND) | Medium | Left to right |
|| (OR) | Lower | Left to right |
?? (Nullish) | Lowest | Left to right |
// 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
// 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
// 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
// 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
// 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-circuiting | Use if statement |
|---|---|
| Simple default value | Multiple statements to execute |
| One-line guard check | Complex branching logic |
| Inline conditional in template | Side effects that need clarity |
| Chaining 2-3 fallbacks | Deeply nested conditions |
// 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
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 when0,"", orfalseare valid and should be preserved- They return values, not booleans:
"hello" || "world"returns"hello", nottrue - Mixing
??with||/&&requires parentheses: JavaScript throws a SyntaxError without them
Frequently Asked Questions
Does short-circuiting improve performance?
Can I use short-circuiting with async/await?
Why does JavaScript return values instead of booleans from logical operators?
What is the difference between && and optional chaining (?.)?
Can I chain more than two ?? operators?
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.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.