JAVASCRIPT / OPERATORS
Assignment and compound assignment
Use = and the compound operators, from += to ??=, knowing what each one reads, when it writes, and which base operator's rules it inherits.
What you will learn
- Read x += y as read, combine, write, with the target resolved exactly once
- Predict += on mixed types by falling back to the plain + rules
- Choose ??= or ||= when the write itself should be skipped, not just the value
- Spot let a = b = 0 as one declaration plus a stray assignment
Understanding Assignment and compound assignment
Assignment in JavaScript is an operator, not a statement: total = 5 produces a value, namely the value that was stored. That is why a = b = 0 works at all, and why it groups right to left: b = 0 runs first, and its result 0 is what lands in a. The left side has to be something that can hold a value, such as a declared binding, an object property, or an array element. The write replaces only what that slot holds, so pointing a variable at a new object never disturbs the object it used to reference.
A compound assignment is a read, a combine, and a write back into the same slot. The engine resolves the target once, fetches its current value, evaluates the right side, applies the base operator, and stores the result. Because the base operator does the actual work, += behaves exactly like +, %= like %, and no hidden numeric conversion is added anywhere. That is why total += "1" concatenates while total -= "1" subtracts: each compound form inherits its operator's coercion rules instead of inventing new ones.
The logical family, &&= and ||= and ??=, breaks the pattern in one specific way: the write is conditional. job.mode ??= "auto" reads mode, and if it holds anything other than null or undefined the assignment step is skipped entirely, so no setter fires and no property is created. Keep the split in mind: the arithmetic compound operators always store something, the logical ones may store nothing at all.
let total = 10;
total += 5; // 15
total -= 3; // 12
total *= 2; // 24
total /= 8; // 3
total **= 3; // 27
total %= 4; // 3
console.log(total);
// += is the + operator, so one string operand turns the result into text
let label = "total=";
label += total;
console.log(label);
// = evaluates to the value it stored, and groups right to left
let a, b;
a = b = total * 2;
console.log(a, b);
// logical assignment writes only when the test passes
const job = { retries: 0, name: null };
job.retries ||= 3; // 0 is falsy, so it writes
job.name ??= "batch"; // null is nullish, so it writes
job.name ??= "ignored"; // already set, so nothing is written
console.log(job.retries, job.name);A compound assignment resolves its target once and then reads, applies its base operator, and writes back, so it is shorthand for the write rather than a textual expansion of the expression.
Worked examples
Accumulating with += over mixed types
Shows how a single string operand makes an accumulator drift from number to text, and how converting first fixes it.
const inputs = ["5", "10", 7];
let sum = 0;
for (const value of inputs) {
sum += value;
}
console.log(sum, typeof sum);
let realSum = 0;
for (const value of inputs) {
realSum += Number(value);
}
console.log(realSum, typeof realSum);Example explained
Line 1The first += evaluates 0 + "5", and since one side is a string the result is the text "05".
Line 2Every later pass now has a string on the left, so the loop keeps appending instead of adding.
Line 3Number(value) converts before the operator runs, so += stays on the numeric branch of + and reaches 22.
Line 4Swapping += for -= would also give 22, because - has no string behaviour to fall back on.
The target is evaluated once
Compares a compound assignment with its hand-written expansion when the target index has a side effect.
const scores = [10, 20, 30];
let i = 0;
scores[i++] += 100;
console.log(scores.join(","), i);
let j = 0;
scores[j++] = scores[j++] + 1000;
console.log(scores.join(","), j);Example explained
Line 1scores[i++] += 100 runs i++ a single time: it yields index 0 and leaves i at 1.
Line 2The old value 10 is read from scores[0] and 110 is written back to that same slot.
Line 3The expanded version runs j++ twice, so it reads scores[1] (20) but writes to scores[0].
Line 4That is the practical payoff of the compound form: the read and the write cannot drift apart.
Logical assignment can skip the write
Demonstrates that ||= does not perform an assignment when the current value already passes the test.
const settings = {
_theme: "dark",
get theme() {
return this._theme;
},
set theme(value) {
console.log("setter ran with:", value);
this._theme = value;
}
};
settings.theme ||= "light";
settings.theme = settings.theme || "light";
console.log(settings.theme);Example explained
Line 1settings.theme ||= "light" calls the getter, sees the truthy "dark", and stops there.
Line 2No write happens, which is why the setter prints nothing for that line.
Line 3The hand-written version always assigns, so the setter runs even though the value is unchanged.
Line 4On properties backed by setters or proxies, that redundant write can trigger real work.
Important notes
const blocks rebinding the name, not mutating the value: job.retries += 1 is legal on a const object while job += 1 is not.
&&=, ||= and ??= arrived in ES2021 and may skip the write entirely, so do not use them where a property's setter has to run.
Common mistakes
Feeding form or JSON values straight into total += value: starting from 0 with "5" and "10" produces the string "0510" instead of 15, and later comparisons quietly misbehave.
Declaring a counter with const and later writing count += 1, which throws TypeError: Assignment to constant variable and stops the script.
Typing count =+ 5 instead of count += 5: it parses as an assignment of unary plus 5, so the counter is silently reset to 5 every time rather than growing.
Try it yourself
Change, predict, then run
In a browser console, declare let basket = { items: 2, coupon: "" }; and use only compound assignments to triple items and then add 4 more. Reset coupon to "" between tries and run basket.coupon ||= "NONE" and basket.coupon ??= "NONE" separately to see which one replaces an empty string.
Open the JavaScript workspaceCheck your understanding
Given const row = [1, 2, 3]; let i = 0; row[i++] += 10; what does row hold afterwards, and why?
- [11, 2, 3], because the compound form evaluates the target row[i++] once, so the read and the write hit the same slot
- [1, 12, 3], because += expands to row[i++] = row[i++] + 10, which reads and writes different slots
- [11, 2, 3], because i++ produces 1 and array indexes start at zero, so it corrects down to row[0]
- [1, 2, 3], because compound assignment cannot write through a computed index on a const array
Show answer
The left-hand side is resolved a single time: i++ yields 0 and leaves i at 1, so 1 is read from row[0] and 11 is written back there. The second option describes the hand-expanded version, which is exactly what the compound form avoids, and the fourth confuses const with immutability: const only stops row from being rebound, not from having its elements written.