What are logical assignment operators?
Assesses fundamental understanding of JavaScript conventions, runtime behavior, and memory/performance considerations.
Hiring managers look for precision, avoidance of ambiguous jargon, and ability to explain trade-offs under real production conditions.
Logical assignment operators (introduced in ES2021) combine logical operations (&&, ||, ??) with assignment (=). They provide a concise way to assign values based on logical conditions.
The Three Operators:
&&=- Logical AND assignment||=- Logical OR assignment??=- Nullish coalescing assignment
Logical AND Assignment (&&=):
Assigns the right-hand value only if the left-hand value is truthy.
// Syntax: x &&= y
// Equivalent to: x && (x = y)
// or: if (x) { x = y; }
let user = { name: 'Alice', admin: true };
// Traditional approach
if (user.admin) {
user.admin = 'super';
}
// With &&=
user.admin &&= 'super';
console.log(user.admin); // 'super'
let guest = { name: 'Bob', admin: false };
guest.admin &&= 'super';
console.log(guest.admin); // false (unchanged, because falsy)
// Practical example: Conditional transformation
const data = {
username: 'john_doe',
email: 'JOHN@EXAMPLE.COM'
};
// Normalize email only if it exists
data.email &&= data.email.toLowerCase();
console.log(data.email); // 'john@example.com'
// Use case: Applying transformations
const product = { name: 'Widget', price: 29.99 };
product.price &&= product.price * 1.1; // Apply 10% increase
console.log(product.price); // 32.989
Logical OR Assignment (||=):
Assigns the right-hand value only if the left-hand value is falsy.
// Syntax: x ||= y
// Equivalent to: x || (x = y)
// or: if (!x) { x = y; }
let config = { timeout: 0 };
// Traditional approach
if (!config.timeout) {
config.timeout = 3000;
}
// With ||=
config.timeout ||= 3000;
console.log(config.timeout); // 3000
// Setting default values
let options = {};
options.theme ||= 'dark';
options.lang ||= 'en';
options.debug ||= false;
console.log(options); // { theme: 'dark', lang: 'en', debug: false }
// Practical example: Form defaults
function processForm(formData) {
formData.country ||= 'USA';
formData.newsletter ||= false;
formData.age ||= 18;
return formData;
}
console.log(processForm({ name: 'Alice' }));
// { name: 'Alice', country: 'USA', newsletter: false, age: 18 }
// Use case: Lazy initialization
class Calculator {
#cache;
compute(x) {
this.#cache ||= new Map(); // Initialize only once
if (!this.#cache.has(x)) {
this.#cache.set(x, x * x);
}
return this.#cache.get(x);
}
}
Nullish Coalescing Assignment (??=):
Assigns the right-hand value only if the left-hand value is null or undefined (nullish).
// Syntax: x ??= y
// Equivalent to: x ?? (x = y)
// or: if (x === null || x === undefined) { x = y; }
let settings = { volume: 0, brightness: null };
// Traditional approach
if (settings.brightness === null || settings.brightness === undefined) {
settings.brightness = 50;
}
// With ??=
settings.volume ??= 50; // Unchanged (0 is not nullish)
settings.brightness ??= 50; // Changed (null is nullish)
console.log(settings); // { volume: 0, brightness: 50 }
// Key difference from ||=
let data = {
count: 0,
active: false,
name: ''
};
// With ||= (treats falsy values as missing)
let copy1 = { ...data };
copy1.count ||= 10; // Changes to 10 (0 is falsy)
copy1.active ||= true; // Changes to true (false is falsy)
copy1.name ||= 'Unknown'; // Changes to 'Unknown' ('' is falsy)
// With ??= (only treats null/undefined as missing)
let copy2 = { ...data };
copy2.count ??= 10; // Stays 0 (not nullish)
copy2.active ??= true; // Stays false (not nullish)
copy2.name ??= 'Unknown'; // Stays '' (not nullish)
console.log(copy1); // { count: 10, active: true, name: 'Unknown' }
console.log(copy2); // { count: 0, active: false, name: '' }
// Practical example: API defaults
function fetchUser(userId, options = {}) {
options.cache ??= true;
options.timeout ??= 5000;
options.retries ??= 3;
// Note: Won't override if explicitly set to 0 or false
console.log('Fetching with options:', options);
}
fetchUser(1, { cache: false });
// { cache: false, timeout: 5000, retries: 3 }
// cache stays false (not nullish)
Comparison Table:
let obj = { a: 0, b: false, c: '', d: null, e: undefined };
// &&= (assigns if truthy)
obj.a &&= 100; // Unchanged (0 is falsy)
obj.b &&= 100; // Unchanged (false is falsy)
obj.c &&= 100; // Unchanged ('' is falsy)
// ||= (assigns if falsy)
obj.a ||= 100; // Changes to 100
obj.b ||= 100; // Changes to 100
obj.c ||= 100; // Changes to 100
// ??= (assigns if nullish)
obj.a ??= 100; // Unchanged (0 is not nullish)
obj.b ??= 100; // Unchanged (false is not nullish)
obj.c ??= 100; // Unchanged ('' is not nullish)
obj.d ??= 100; // Changes to 100 (null is nullish)
obj.e ??= 100; // Changes to 100 (undefined is nullish)
Real-World Examples:
// 1. Component state management
class Component {
state = {};
setState(newState) {
// Merge with defaults
newState.loading ??= false;
newState.error ??= null;
newState.data ??= [];
this.state = { ...this.state, ...newState };
}
}
// 2. Configuration merging
function createConfig(userConfig) {
const config = { ...userConfig };
config.env ??= 'production';
config.debug ??= false;
config.port ??= 3000;
config.host ??= 'localhost';
return config;
}
// 3. Memoization
const memoize = (fn) => {
const cache = new Map();
return (arg) => {
cache.has(arg) ||= cache.set(arg, fn(arg));
return cache.get(arg);
};
};
// 4. Safe property updates
function updateUser(user, updates) {
user.lastModified &&= new Date(); // Only if already has lastModified
user.email ??= updates.email; // Only if email is missing
user.role ||= 'user'; // Only if role is falsy
return user;
}
Benefits:
- Concise: Shorter than traditional if statements
- Readable: Clear intent - "assign if condition"
- Safe: Avoids unnecessary assignments and side effects
- Performance: Only evaluates right-hand side when needed
Candidate Response Strategy & Interview Tips
- Start with a concise one-sentence summary: Deliver a direct, confident answer first before expanding into nuances.
- Demonstrate real-world trade-offs: Discuss where this approach excels and when you would avoid it in production systems.
- Discuss complexity & edge cases: Proactively explain time/space complexity or boundary conditions (null values, scale limits).
- Prepare for interviewer follow-ups: Technical hiring panels frequently probe deeper into concurrency, backward compatibility, or alternative libraries.