Topics in this subject
JavaScript 3 min read Updated 4 Aug 2026

Part 6 — Operators

Arithmetic, Assignment, Comparison, Logical & short-circuiting, Nullish coalescing ??

Arithmetic#

+ - * / % ** ++ --. Notes: ** is exponentiation (2 ** 10 === 1024); % is remainder, not modulo, so it keeps the sign of the dividend (-5 % 3 === -2).

Assignment#

= += -= *= /= %= **= &&= ||= ??= &= |= ^= <<= >>= >>>=

Logical assignment (ES2021) 🟢:

a ||= b;   // a = a || b   (assign if a is falsy)
a &&= b;   // a = a && b   (assign if a is truthy)
a ??= b;   // a = a ?? b   (assign only if a is null/undefined)

Comparison#

Operator Meaning Coerces?
== loose equality yes ⚠️
=== strict equality no 🟢
!= / !== inequalities as above
< > <= >= ordering yes (to number, or string compare)

🟢 Always use ===/!==. The only common exception is x == null, which neatly checks "null or undefined" in one go.

0 == "0"        // true  ⚠️ (coercion)
0 == ""         // true  ⚠️
0 == false      // true  ⚠️
null == undefined // true (special-cased)
null == 0       // false ⚠️ (null only loosely equals undefined)
NaN === NaN     // false — use Number.isNaN() or Object.is()

Logical & short-circuiting#

a && b   // if a falsy → a, else b
a || b   // if a truthy → a, else b
!a       // boolean negation

Short-circuiting: && stops at the first falsy, || at the first truthy — the right side may never run. Used for guards and defaults:

user && user.save();          // call only if user exists (pre-optional-chaining)
const name = input || "Guest"; // ⚠️ falls back for "", 0, false too

Nullish coalescing ??#

Returns the right side only when the left is null or undefined — unlike ||, it respects 0, "", and false:

const count = userCount ?? 10;   // 0 stays 0
const label = title || "N/A";    // "" becomes "N/A"  (often a bug!)

⚠️ You can't mix ?? with ||/&& without parentheses: a ?? b || c is a SyntaxError.

Optional chaining ?.#

Short-circuits to undefined if the thing before it is null/undefined:

user?.address?.city         // undefined instead of throwing
user?.getName?.()           // call only if it exists
arr?.[0]                    // safe indexing

⚠️ ?. only guards the access immediately after it. a?.b.c still throws if a.b is null. And ?. does not protect assignments.

Spread ... and rest ...#

Same token, opposite jobs:

// Spread — expands
const merged = { ...a, ...b };       // later keys win
const clone  = [...arr];             // shallow copy
Math.max(...[3, 1, 2]);              // 3

// Rest — collects
function sum(...nums) { return nums.reduce((a, b) => a + b, 0); }
const [first, ...others] = [1, 2, 3];   // others = [2, 3]
const { id, ...restProps } = obj;

Bitwise#

& | ^ ~ << >> >>>. Operate on 32-bit integers. Handy tricks: x | 0 and ~~x truncate to int (🔴 prefer Math.trunc), n & 1 tests oddness, a ^ b ^ a === b. >>> is the unsigned right shift.

delete, void, in, instanceof#

const o = { a: 1 };
delete o.a;            // removes property → true; returns true even if absent
void 0;                // evaluates operand, returns undefined
"a" in o;              // does key exist (including inherited)?  → false now
[] instanceof Array;   // prototype-chain check

⚠️ delete on array elements leaves a hole (empty slot), it does not reindex — use splice to remove array items.

Ternary and comma#

const r = cond ? "yes" : "no";
let x = (1, 2, 3);   // comma evaluates all, returns last → 3 (rarely useful)

Operator precedence & coercion#

Full precedence is a table you rarely memorize — when in doubt, add parentheses. The high-value facts: member access ./?. and () bind tightest; unary before binary; ** is right-associative (2 ** 3 ** 2 === 512); assignment is lowest and right-associative; ?? cannot mix with ||/&&.

Interview questions (Part 6):

  1. Difference between || and ??? Give a bug || causes that ?? fixes.
  2. Why is NaN === NaN false, and how do you test for NaN?
  3. What does optional chaining protect and what does it not protect?
  4. Predict: 2 ** 3 ** 2. Explain associativity.