Logical Assignment Operators in JS Complete Guide
A complete guide to JavaScript logical assignment operators. Covers ||=, &&=, and ??= syntax, how each differs from regular assignment, short-circuit behavior, practical default-filling and conditional-update patterns, combining with optional chaining, and migration from older idioms.
ES2021 introduced three logical assignment operators: ||=, &&=, and ??=. They combine a logical check with an assignment in a single, short-circuiting expression. Each one conditionally assigns based on the current value of the left-hand side.
The Three Operators at a Glance
| Operator | Name | Equivalent To | Assigns When Left Side Is |
|---|---|---|---|
a ||= b | Logical OR assignment | a || (a = b) | Falsy |
a &&= b | Logical AND assignment | a && (a = b) | Truthy |
a ??= b | Nullish coalescing assignment | a ?? (a = b) | Null or undefined |
The key difference from a = a || b is short-circuit behavior: the assignment itself is skipped if the condition is not met.
Logical OR Assignment (||=)
Assigns the right side only when the left side is falsy (false, 0, "", null, undefined, NaN):
let title = "";
title ||= "Untitled";
console.log(title); // "Untitled" — empty string is falsy
let count = 0;
count ||= 10;
console.log(count); // 10 — 0 is falsy (use ??= if 0 is valid)
let name = "Alice";
name ||= "Default";
console.log(name); // "Alice" — truthy, assignment skippedPractical: Filling Missing Display Values
function renderCard(data) {
data.title ||= "No Title";
data.description ||= "No description available.";
data.thumbnail ||= "/images/placeholder.png";
return data;
}Logical AND Assignment (&&=)
Assigns the right side only when the left side is truthy:
let session = { user: "Alice", token: "abc123" };
session &&= { ...session, lastActivity: Date.now() };
console.log(session); // { user: "Alice", token: "abc123", lastActivity: ... }
let expired = null;
expired &&= { ...expired, lastActivity: Date.now() };
console.log(expired); // null — falsy, assignment skippedPractical: Conditional Transformation
let errorMessage = "Network timeout";
// Only transform if there's an existing message
errorMessage &&= `Error: ${errorMessage}`;
console.log(errorMessage); // "Error: Network timeout"
let noError = "";
noError &&= `Error: ${noError}`;
console.log(noError); // "" — falsy, assignment skippedPractical: Sanitizing Truthy Values
const form = {
name: " Alice ",
email: "alice@example.com",
bio: "",
};
// Only trim non-empty strings
form.name &&= form.name.trim();
form.email &&= form.email.trim();
form.bio &&= form.bio.trim(); // skipped — "" is falsyNullish Coalescing Assignment (??=)
Assigns the right side only when the left side is null or undefined:
let timeout = 0;
timeout ??= 3000;
console.log(timeout); // 0 — not nullish, kept
let retries = null;
retries ??= 3;
console.log(retries); // 3 — null is nullish, assigned
let host = undefined;
host ??= "localhost";
console.log(host); // "localhost" — undefined is nullish, assignedPractical: Config Object Defaults
function initConfig(config = {}) {
config.host ??= "localhost";
config.port ??= 3000;
config.debug ??= false;
config.retries ??= 3;
config.timeout ??= 5000;
return config;
}
initConfig({ port: 0, debug: true });
// { port: 0, debug: true, host: "localhost", retries: 3, timeout: 5000 }
// port=0 is preserved (not nullish)See advanced JS nullish coalescing full tutorial for the non-assignment version of ??.
Short-Circuit Behavior
The assignment itself is a side effect that is skipped when the condition is not met. This matters for setters and proxies:
const obj = {
_value: "existing",
set value(v) {
console.log("setter called with:", v);
this._value = v;
},
get value() {
return this._value;
},
};
// ??= does NOT trigger the setter (left side is not nullish)
obj.value ??= "new";
// No "setter called" log — setter was never invoked
// Compare with: obj.value = obj.value ?? "new";
// This ALWAYS triggers the setter, even when the value doesn't changeThis is the critical difference from a = a ?? b: the logical assignment version avoids unnecessary writes.
Choosing the Right Operator
| Goal | Operator | Example |
|---|---|---|
| Fill in any missing/falsy value | ||= | title ||= "Untitled" |
| Update only if value exists | &&= | session &&= refresh(session) |
| Fill in only null/undefined | ??= | config.port ??= 3000 |
Decision Guide
Is 0, "", or false a valid value you want to keep?
├── Yes → Use ??= (only replaces null/undefined)
└── No → Use ||= (replaces all falsy values)
Want to transform only when value is already set?
└── Yes → Use &&=
Migration From Older Idioms
// Old: if-based default
if (obj.x === undefined || obj.x === null) {
obj.x = defaultValue;
}
// New:
obj.x ??= defaultValue;
// Old: OR-based default
obj.x = obj.x || defaultValue;
// New:
obj.x ||= defaultValue;
// Old: conditional update
if (obj.x) {
obj.x = transform(obj.x);
}
// New:
obj.x &&= transform(obj.x);Common Mistakes
Confusing ??= With ||=
const settings = { volume: 0 };
settings.volume ||= 50; // 50 — BUG: 0 is valid but was overwritten
settings.volume ??= 50; // 0 — correct: 0 is preservedExpecting ??= to Work With Deep Paths
const obj = {};
// obj.a.b ??= "value"; // TypeError: Cannot read property 'b' of undefined
// You need optional chaining beforehand, but ??= does not chain:
if (obj.a != null) {
obj.a.b ??= "value";
}
// Or initialize the path first:
obj.a ??= {};
obj.a.b ??= "value";Rune AI
Key Insights
- ??= is the safest default filler: It only assigns when the left side is null or undefined, preserving valid falsy values like 0 and ""
- ||= replaces all falsy values: Use when 0, "", and false should also trigger the default
- &&= transforms only existing values: It skips the assignment entirely when the left side is falsy
- Short-circuit prevents unnecessary setter calls: Unlike
a = a ?? b, logical assignment avoids invoking setters when no change is needed - Use ??= for config initialization:
config.port ??= 3000is the concise, correct way to fill missing configuration values
Frequently Asked Questions
Are logical assignment operators supported in all browsers?
Can I use these with array elements?
Do these work with destructured variables?
Is there a performance benefit over the long form?
Can I chain logical assignment operators?
Conclusion
Logical assignment operators condense common default-filling and conditional-update patterns into single expressions. Use ??= for filling null/undefined gaps (the most common case), ||= for replacing all falsy values, and &&= for transforming only when a value exists. Their short-circuit behavior avoids unnecessary writes, making them superior to the a = a || b idiom. For the nullish coalescing operator itself, see advanced JS nullish coalescing full tutorial. For optional chaining that pairs with these operators, see advanced JS optional chaining complete guide.
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.