JavaScript Easy technical 0 views 4 min read

What are logical assignment operators?

Peer-reviewed by HireXTech Technical Panel Updated for 2025/2026 hiring Editorial standards
Practise this track
Interviewer Expectations for this Question
01
Core Competency

Assesses fundamental understanding of JavaScript conventions, runtime behavior, and memory/performance considerations.

02
Evaluation Criteria

Hiring managers look for precision, avoidance of ambiguous jargon, and ability to explain trade-offs under real production conditions.

Comprehensive Model Answer Verified Solution

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:

  1. &&= - Logical AND assignment
  2. ||= - Logical OR assignment
  3. ??= - 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:

  1. Concise: Shorter than traditional if statements
  2. Readable: Clear intent - "assign if condition"
  3. Safe: Avoids unnecessary assignments and side effects
  4. Performance: Only evaluates right-hand side when needed

Candidate Response Strategy & Interview Tips

  1. Start with a concise one-sentence summary: Deliver a direct, confident answer first before expanding into nuances.
  2. Demonstrate real-world trade-offs: Discuss where this approach excels and when you would avoid it in production systems.
  3. Discuss complexity & edge cases: Proactively explain time/space complexity or boundary conditions (null values, scale limits).
  4. Prepare for interviewer follow-ups: Technical hiring panels frequently probe deeper into concurrency, backward compatibility, or alternative libraries.
Related Topics & Skills
Spotted an error or have an alternative solution?