JavaScriptAssignment Operators

Assignment Operators

Assignment is how a variable gets a value. The plain = is the workhorse, but JavaScript has a family of compound operators that combine assignment with another operation — +=, ||=, ??= and so on — plus destructuring assignment, which can pull values out of objects and arrays in a single statement.

Plain assignment

JS
let total = 0;
total = 100;        // re-assignment

const user = { name: "Ada" };
user.name = "Lin";  // assigning to a property (allowed even though user is const)

const arr = [1, 2, 3];
arr[0] = 99;        // assigning to an index

The left-hand side of = must be a target — a variable, a property, an array index, or a destructuring pattern. The right-hand side is any expression.

Assignment is itself an expression
The whole `x = 5` *expression* evaluates to `5`, which is why you can chain assignments (below) or use `(x = something)` inside a condition. Using assignment-in-condition by accident is a common bug — most linters flag it.
Compound assignment

Every arithmetic and bitwise operator has a compound form that reads "do the operation and store the result".

JS
let n = 10;
n += 5;    // n = n + 5  → 15
n -= 3;    // n = n - 3  → 12
n *= 2;    // n = n * 2  → 24
n /= 4;    // n = n / 4  → 6
n %= 4;    // n = n % 4  → 2
n **= 3;   // n = n ** 3 → 8

let s = "foo";
s += "bar"; // s = s + "bar" → "foobar"

// Bitwise variants exist too
let b = 0b1010;
b &= 0b1100;   // 0b1000
b |= 0b0011;   // 0b1011
b ^= 0b1010;   // 0b0001
b <<= 2;       // 0b0100
b >>= 1;       // 0b0010
b >>>= 0;      // unsigned right shift
Logical assignment

Added in ES2021, these combine short-circuiting with assignment. They only assign when the variable's current value clears a particular check.

JS
// ||=  — assign if the current value is FALSY
let a = "";
a ||= "default";        // a is now "default"

// &&=  — assign if the current value is TRUTHY
let b = "ready";
b &&= b.toUpperCase();  // b is now "READY"

// ??=  — assign if the current value is null or undefined
let c = 0;
c ??= 100;              // c is still 0  (0 is not nullish)

let d = null;
d ??= 100;              // d is now 100

??= is the safest of the three for "set a default unless something was explicitly assigned" — it doesn't fire on 0, "" or false.

Lazy initialisation pattern

JS
function getCache() {
  cache ??= new Map();   // create on first call only
  return cache;
}
let cache;
Chained assignment

Because = returns the assigned value, you can write a = b = c = 0 to set them all to zero.

JS
let a, b, c;
a = b = c = 0;
console.log(a, b, c);
0 0 0
Watch the declaration
`let a = b = c = 0` only declares `a`. `b` and `c` end up as *globals* (or a `ReferenceError` in strict mode), because they were never declared with `let`. Always declare every variable on its own, or use destructuring (next section).
Destructuring assignment

A pattern on the left of = can pull values out of arrays and objects in one shot. It is one of the most beloved features of modern JavaScript.

Array destructuring

JS
const [first, second, third] = [10, 20, 30];
console.log(first, second, third); // 10 20 30

// Skip elements with commas
const [, , c] = [1, 2, 3];
console.log(c); // 3

// Rest to collect the tail
const [head, ...rest] = [1, 2, 3, 4];
console.log(head, rest); // 1 [2, 3, 4]

// Defaults if the value is undefined
const [a = 1, b = 2] = [10];
console.log(a, b); // 10 2

// Swap two variables — no temp needed
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1

Object destructuring

JS
const user = { name: "Ada", role: "admin", age: 36 };

// Pull out properties
const { name, role } = user;
console.log(name, role); // Ada admin

// Rename
const { name: userName } = user;
console.log(userName); // Ada

// Defaults
const { country = "UK" } = user;
console.log(country); // UK

// Rest
const { age, ...rest } = user;
console.log(age, rest); // 36 { name: "Ada", role: "admin" }
Destructuring in function parameters

Where destructuring really pays off: cleanly accepting an "options" object.

JS
function createUser({ name, role = "user", active = true } = {}) {
  return { name, role, active };
}

createUser({ name: "Ada" });
// { name: "Ada", role: "user", active: true }

createUser({ name: "Lin", role: "admin" });
// { name: "Lin", role: "admin", active: true }

The = {} at the end of the parameter list lets you call createUser() with no argument at all — the function still finds an empty object to destructure.

Re-assigning to const targets

const prevents re-binding; it doesn't prevent mutation or property assignment.

JS
const user = { name: "Ada" };
user.name = "Lin";       // ok — mutating the object
user.role = "admin";     // ok — adding a property
// user = { name: "Lin" };  // TypeError — re-binding

const arr = [1, 2, 3];
arr.push(4);             // ok — mutation
arr[0] = 99;             // ok
// arr = [];             // TypeError
Operator chaining gotcha

JS
// Pitfall: easy to write = instead of ==
if (user.role = "admin") {  // ASSIGNS "admin" to user.role, then evaluates "admin" (truthy)
  // always runs!
}

// Correct:
if (user.role === "admin") {
  // ...
}
Lint protects you here
Most linters' `no-cond-assign` rule catches this exact mistake. Turn it on — `=` in a condition is almost always a typo.
A clean mental model
Read `x += y` as "increase `x` by `y`", read `x ??= y` as "set `x` to `y` if it had no value", and read destructuring as "the left pattern shaped like the right value". That's enough vocabulary to handle almost any line of modern JavaScript.