Polyglot
Languages Web (HTML / CSS / JS) operators
Web (HTML / CSS / JS) § operators

Operators

JavaScript’s operator surface is C-family at the core, with substantial additions: strict equality (===/!== — distinct from coercive ==/!=), nullish coalescing (?? — admit “use this if null/undefined”), optional chaining (?. — admit “access if not nullish”), spread/rest (... — admit substantial unpacking), exponentiation (**), logical assignment (&&=, ||=, ??=). The conventional contemporary discipline: always ===, never == (except == null); ?? over || for defaults; ?. for substantial nil-safe access. The combination — C-family arithmetic and comparison, the strict equality discipline, the optional/nullish chain, the substantial spread mechanism — is the substance of JavaScript’s expression surface.

Arithmetic

a + b                                              // addition (and string concat!)
a - b                                              // subtraction
a * b                                              // multiplication
a / b                                              // division (always float; no integer division)
a % b                                              // remainder
a ** b                                             // exponentiation (ES2016)

-a                                                 // unary negation
+a                                                 // unary plus (numeric coercion)
++a                                                // pre-increment
a++                                                // post-increment
--a                                                // pre-decrement
a--                                                // post-decrement

The principal pitfall: + is both numeric addition and string concatenation:

1 + 2;                                             // 3 (numeric)
"1" + 2;                                           // "12" (concat)
"1" - 2;                                           // -1 (numeric; - has no string op)

[] + [];                                           // "" (both coerce to empty string)
[] + {};                                           // "[object Object]"
{} + [];                                           // 0 (block + array)

The conventional defence: explicit conversion via Number(), String():

Number(input) + 5;                                 // explicit numeric
`${input} suffix`;                                 // explicit string (template literal)

For BigInt, the operators must use BigInt operands:

1n + 2n;                                           // 3n
1n + 2;                                            // TypeError
1n + BigInt(2);                                    // 3n

Comparison

a == b                                             // loose equality (coerces) — AVOID
a != b                                             // loose inequality — AVOID
a === b                                            // strict equality (no coercion)
a !== b                                            // strict inequality
a < b
a > b
a <= b
a >= b

The principal pitfalls of loose equality:

1 == "1";                                          // true (coerced)
1 == true;                                         // true (coerced)
0 == false;                                        // true
null == undefined;                                 // true (both nullish)
"" == 0;                                           // true (substantial weirdness)
[] == false;                                       // true
[1] == 1;                                          // true
"  " == 0;                                         // true ("  " → 0 → false → 0)

// vs strict:
1 === "1";                                         // false
1 === true;                                        // false
null === undefined;                                // false

The conventional contemporary discipline: always use === and !==. The exception:

if (value == null) { ... }                         // matches both null and undefined

Logical operators

a && b                                             // AND (short-circuit, returns operand)
a || b                                             // OR (short-circuit, returns operand)
!a                                                 // NOT
a ?? b                                             // nullish coalescing (ES2020)

The && and || return one of the operands, not just true/false:

true && "yes";                                     // "yes"
false && "yes";                                    // false
"a" && "b";                                        // "b"

null || "default";                                 // "default"
0 || "default";                                    // "default" (0 is falsy)
"" || "default";                                   // "default"
"value" || "default";                              // "value"

The ?? returns the right only if the left is null or undefined:

0 ?? "default";                                    // 0 (0 is not nullish)
"" ?? "default";                                   // "" (empty string is not nullish)
null ?? "default";                                 // "default"
undefined ?? "default";                            // "default"

The conventional contemporary discipline:

  • || — substantial “any falsy → default” patterns.
  • ?? — substantial “null/undefined → default” patterns; admits 0, "", false as valid.
const port = config.port ?? 8080;                  // admits port=0
const port = config.port || 8080;                  // wrong if port=0 is valid

const name = user.name ?? "anonymous";             // admits name=""

Bitwise operators

a & b                                              // AND
a | b                                              // OR
a ^ b                                              // XOR
~a                                                 // NOT
a << b                                             // left shift
a >> b                                             // signed right shift
a >>> b                                            // unsigned right shift (JS-specific)

JavaScript bitwise operators coerce to 32-bit signed integers — admit substantial pitfalls for large numbers:

2 ** 32 | 0;                                       // 0 (overflow)
0xFFFFFFFF | 0;                                    // -1 (signed)

// Common bit-twiddling:
n | 0;                                             // truncate to 32-bit int (faster than Math.floor for some cases)
n << 0;                                            // same
~~n;                                               // double-NOT (truncate)

For BigInt, bitwise operators admit arbitrary precision:

0xFFFFFFFFn | 0n;                                  // 0xFFFFFFFFn (no overflow)

Assignment operators

a = b                                              // basic
a += b                                             // a = a + b
a -= b
a *= b
a /= b
a %= b
a **= b

a &= b                                             // bitwise
a |= b
a ^= b
a <<= b
a >>= b
a >>>= b

// Logical assignment (ES2021):
a &&= b                                            // a = a && b
a ||= b                                            // a = a || b
a ??= b                                            // a = a ?? b

The logical-assignment forms admit substantial patterns:

config.timeout ??= 30;                             // set if null/undefined
items[key] ||= [];                                 // set if any falsy
flag &&= condition;                                // toggle off

Spread ...

The ... admits spread in calls, literals, destructuring:

// In array literal:
const a = [1, 2, 3];
const b = [...a, 4, 5];                            // [1, 2, 3, 4, 5]

// In object literal:
const obj1 = { x: 1, y: 2 };
const obj2 = { ...obj1, z: 3 };                    // { x: 1, y: 2, z: 3 }

// In function call:
console.log(...["a", "b", "c"]);                   // a b c
Math.max(...[3, 1, 4]);                            // 4

// In destructuring (rest):
const [first, ...rest] = [1, 2, 3, 4];             // first=1, rest=[2,3,4]
const { name, ...others } = { name: "Alice", age: 30, role: "admin" };
// name="Alice", others={age:30, role:"admin"}

// In function parameters (rest):
function sum(...nums) {
    return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3);                                      // 6

Optional chaining ?.

The ?. admits “access this if not nullish”:

const user = getUser();

const name = user?.name;                           // undefined if user is null/undefined
const street = user?.address?.street;              // chained
const first = list?.[0];                           // bracket access
const result = fn?.(arg);                          // function call

// Without ?. (substantial verbosity):
const street = user && user.address && user.address.street;

The form short-circuits to undefined if any link is nullish.

For delete and call:

delete obj?.prop;                                  // no-op if obj is nullish

obj.method?.();                                    // call only if method exists
optionalCallback?.(arg);                           // call if callback is set

The conventional contemporary discipline: combine with ?? for safe-access-with-default:

const name = user?.name ?? "anonymous";
const port = config?.server?.port ?? 8080;

Ternary ?:

const max = a > b ? a : b;
const status = active ? "running" : "stopped";

// Nested (avoid — substantial readability burden):
const sign = n > 0 ? "positive" : n < 0 ? "negative" : "zero";

Comma operator

const x = (a = 1, b = 2, a + b);                   // x = 3 (last expression)

// In for loops:
for (let i = 0, j = 10; i < j; i++, j--) { ... }

The comma admits multiple expressions in places that admit one — substantial for for loop initialisation/update.

typeof, instanceof, in

Treated more substantially in Types.

typeof value;                                      // string describing type
value instanceof Class;                            // prototype chain check
"key" in obj;                                      // own or inherited property

delete

delete obj.prop;                                   // remove property; returns true
delete obj["key"];

// On arrays:
const arr = [1, 2, 3];
delete arr[1];                                     // arr = [1, undefined, 3]
                                                   // length unchanged; "hole" created

// Conventional alternatives for array removal:
arr.splice(1, 1);                                  // mutate; remove at index
const filtered = arr.filter((_, i) => i !== 1);    // new array

The conventional discipline avoids delete on arrays — admit substantial sparse-array pitfalls.

void

The void operator evaluates its operand and returns undefined:

void 0;                                            // undefined
void compute();                                    // discard the result

// Common use: signal "discard this value":
button.onclick = () => void asyncTask();           // explicit "ignore the promise"

new, this, super

const date = new Date();                           // construct
const obj = new MyClass(args);

// Inside class:
class Child extends Parent {
    constructor() {
        super();                                   // call parent constructor
        this.name = "child";
    }

    method() {
        super.method();                            // call parent method
    }
}

Operator precedence

The principal precedence (high to low):

Operators
(), [], ., ?.
new (with args)
!, ~, +a, -a, ++, --, typeof, void, delete
** (right-associative)
*, /, %
+, -
<<, >>, >>>
<, <=, >, >=, in, instanceof
==, !=, ===, !==
&
^
|
&&
||, ??
?:
=, +=, etc.
, (lowest)

The conventional discipline uses parentheses for clarity in non-trivial expressions.

&& and ?? cannot mix

a && b ?? c;                                       // SYNTAX ERROR
(a && b) ?? c;                                     // OK
a && (b ?? c);                                     // OK

a || b ?? c;                                       // SYNTAX ERROR
(a || b) ?? c;                                     // OK

The mechanism admits substantial precedence-related safety.

Common patterns

Default value

const port = config.port ?? 8080;
const name = user.name ?? "anonymous";
const items = list ?? [];

Optional chain

const city = user?.address?.city;
const first = arr?.[0];
const result = fn?.(arg);

Spread for immutable update

function updateUser(user, changes) {
    return { ...user, ...changes };
}

function addItem(items, newItem) {
    return [...items, newItem];
}

function removeItem(items, idx) {
    return items.filter((_, i) => i !== idx);
}

Spread for max/min

Math.max(...arr);
Math.min(...arr);

For substantial arrays, the spread admits substantial limits:

// Stack overflow for very large arrays:
const max = Math.max(...veryLargeArray);

// Conventional defence:
const max = veryLargeArray.reduce((a, b) => Math.max(a, b), -Infinity);

Destructuring with default

const { name = "anonymous", age = 0 } = user ?? {};

Conditional spread

const obj = {
    a: 1,
    ...(condition && { b: 2 }),
    ...(otherCondition ? { c: 3 } : {}),
};

The pattern admits substantial conditional inclusion.

Function-call with default

function fetch(url, options = {}) {
    const { method = "GET", headers = {}, body = null } = options;
    // ...
}

Logical assignment for memoisation

function getData(key) {
    cache[key] ??= computeExpensive(key);
    return cache[key];
}

Truthiness with &&

const items = data && data.items;                  // null if data is null
const items = data?.items;                         // modern equivalent

// Conditional render (React-style):
{user && <Welcome name={user.name} />}
{user ? <Welcome /> : <SignIn />}

Falsy filter

const truthy = items.filter(Boolean);              // remove null/undefined/0/""/false
const numbers = ["1", "two", "3"].map(Number).filter(Number.isFinite);

Comma for swap (rare)

[a, b] = [b, a];                                   // destructure-based swap (preferred)

Bitwise tricks

n | 0;                                             // truncate to 32-bit int
~n + 1;                                            // negate (two's complement)
n & 1;                                             // odd if 1, even if 0
n & (n - 1) === 0;                                 // power of 2

// Flag combination:
const READ = 1, WRITE = 2, EXECUTE = 4;
const perms = READ | WRITE;                        // 3
const hasRead = (perms & READ) !== 0;              // true
const removed = perms & ~READ;                     // remove READ

Optional method invocation

listener?.dispose?.();
emitter?.emit?.("event", data);

Chained nullish coalescing

const value = first ?? second ?? third ?? "default";

Conditional property

const obj = {
    name: "Alice",
    ...(includeEmail && { email: "alice@b.c" }),
};

A note on == exceptions

The == is admitted for one specific case:

if (value == null) { ... }                         // matches null AND undefined

The pattern admits substantial conciseness — equivalent to value === null || value === undefined.

For all other comparisons, use === / !==.

A note on the conventional discipline

The contemporary JavaScript operator advice:

  • Use === and !== — never == (except == null).
  • Use ?? over || for default values when 0, "", false are valid.
  • Use ?. for nil-safe property access.
  • Use spread ... for immutable updates and unpacking.
  • Use logical assignment (??=, ||=, &&=) for substantial conciseness.
  • Use template literals over + for string concatenation.
  • Use parentheses for clarity in mixed-precedence expressions.
  • Avoid delete on array elements — use splice or filter.
  • Avoid bitwise on numbers > 2^32 — coercion to 32-bit produces unexpected results.
  • Use BigInt for substantial integer arithmetic.

The combination — C-family arithmetic and comparison, strict-equality discipline, the substantial nullish/optional chain (??, ?.), the spread/rest mechanism, the logical-assignment shortcuts, the operator precedence and parenthesisation discipline — is the substance of JavaScript’s expression surface. The discipline produces concise, type-safe-where-possible expressions; the conventional pitfalls (loose equality, type coercion) admit substantial defence through the strict operators and explicit conversions.