|$ curl https://forge-ai.dev/api/markdown?path=docs/js/operators
$cat docs/javascript-—-operators-&-expressions.md
updated This week·30 min read·published

JavaScript — Operators & Expressions

JavaScriptOperatorsBeginnerBeginner to Advanced
Introduction

Operators are symbols that perform operations on operands — values, variables, or expressions. JavaScript provides a comprehensive set of operators spanning arithmetic, comparison, logical, bitwise, assignment, and special-purpose operators. Understanding how each operator works and its precedence in the evaluation order is essential for writing correct expressions.

JavaScript operators can be classified by their arity (unary, binary, ternary) and by their function (arithmetic, comparison, logical, etc.). While most operators are straightforward, JavaScript has several unique behaviors — loose equality coercion, short-circuit evaluation, and the distinction between the nullish coalescing and logical OR operators — that require careful attention.

This page covers every major operator category, the operator precedence table, and the modern operators introduced in ES2020+.

expressions-intro.js
JavaScript
1// An expression produces a value
242; // number literal expression
32 + 3; // arithmetic expression
4a > b ? a : b; // ternary expression
5x && y; // logical expression
6
7// Operators combine expressions
8const result = (5 + 3) * 2 ** 3 / 4 - 1;
9console.log(result); // 15
Arithmetic Operators

Arithmetic operators perform mathematical calculations. JavaScript follows standard math rules with some unique behaviors around type coercion and special numeric values.

OperatorNameExampleResult
+Addition5 + 38
-Subtraction5 - 32
*Multiplication5 * 315
/Division5 / 31.666...
%Remainder (Modulo)5 % 32
**Exponentiation2 ** 416
++Incrementlet x=1; x++1 (returns), 2 (after)
--Decrementlet x=1; x--1 (returns), 0 (after)
+ (unary)Unary Plus+"42"42
- (unary)Unary Negation-"5"-5
arithmetic.js
JavaScript
1// Basic arithmetic
2console.log(10 + 5); // 15
3console.log(10 - 5); // 5
4console.log(10 * 5); // 50
5console.log(10 / 3); // 3.3333333333333335
6console.log(10 % 3); // 1 (remainder)
7console.log(2 ** 10); // 1024 (exponentiation)
8
9// Remainder % with negatives — result takes sign of dividend
10console.log(-5 % 3); // -2
11console.log(5 % -3); // 2
12
13// Increment/decrement — prefix vs postfix
14let a = 0;
15console.log(a++); // 0 (post-increment: returns old value)
16console.log(a); // 1
17
18let b = 0;
19console.log(++b); // 1 (pre-increment: returns new value)
20console.log(b); // 1
21
22// Unary plus — converts to number
23console.log(+"42"); // 42
24console.log(+true); // 1
25console.log(+null); // 0
26console.log(+"hello"); // NaN
27
28// The + operator is overloaded — addition OR concatenation
29console.log(1 + 2); // 3 (addition)
30console.log("1" + 2); // "12" (string concatenation)
31console.log(1 + "2"); // "12"
32console.log(1 + 2 + "3"); // "33" (1+2=3, then 3+"3"="33")
33console.log("1" + 2 + 3); // "123" (left to right)
34
35// Other arithmetic operators always coerce to numbers
36console.log("10" - 2); // 8
37console.log("10" * "2"); // 20
38console.log("10" / 2); // 5

info

The + operator is the only arithmetic operator that does string concatenation. All other arithmetic operators (-, *, /, %, **) coerce both operands to numbers. Use unary + for explicit numeric conversion.
Comparison Operators

Comparison operators compare values and return a boolean. The choice between strict (===, !==) and loose (==, !=) equality is one of the most important decisions in JavaScript programming.

OperatorNameExampleResult
===Strict equality5 === "5"false
==Loose equality5 == "5"true (coerces)
!==Strict inequality5 !== "5"true
!=Loose inequality5 != "5"false (coerces)
>Greater than5 > 3true
>=Greater than or equal5 >= 5true
<Less than3 < 5true
<=Less than or equal4 <= 5true
comparison.js
JavaScript
1// Strict equality — compares value AND type (RECOMMENDED)
2console.log(5 === 5); // true
3console.log(5 === "5"); // false — different types
4console.log(0 === false); // false
5console.log("" === false); // false
6console.log(null === undefined); // false
7console.log(5 !== "5"); // true
8
9// Loose equality — coerces types before comparing (AVOID)
10console.log(5 == "5"); // true
11console.log(0 == false); // true
12console.log("" == false); // true
13console.log(null == undefined); // true (special case)
14
15// Loose equality quirks — reasons to use ===
16console.log([] == false); // true ([] → "" → 0 → false)
17console.log([1] == 1); // true ([1] → "1" → 1)
18console.log([1,2] == "1,2"); // true
19console.log("\t" == 0); // true (whitespace string → 0)
20console.log("\n" == 0); // true
21
22// Object comparison — references compared, not values
23console.log({} === {}); // false
24console.log([] === []); // false
25console.log([1,2] === [1,2]); // false
26
27// Relational operators — coerce to numbers or strings
28console.log("apple" < "banana"); // true — lexicographic string comparison
29console.log("2" > 10); // false — "2" coerces to 2
30console.log("abc" > 0); // false — NaN comparison fails
31console.log(NaN > 0); // false
32console.log(NaN < 0); // false
33console.log(NaN == NaN); // false (I)
34
35// SameValueZero (used by Array.includes, Map, Set)
36console.log(Object.is(NaN, NaN)); // true (SameValue)
37console.log([1, NaN].includes(NaN)); // true (SameValueZero)
38console.log([1, -0].includes(0)); // true (SameValueZero)

warning

The rule is simple: always use === and !==. The only exception is checking for null and undefined simultaneously — value == null is true for both. But even then, being explicit with value === null || value === undefined is clearer.
Logical Operators & Short-Circuit Evaluation

Logical operators work with boolean values but return the actual value of one of the operands, not necessarily a boolean. They use short-circuit evaluation — the right operand is evaluated only if necessary. This behavior is commonly used for conditional execution and default values.

OperatorNameShort-Circuits WhenReturns
&&Logical ANDLeft is falsyFirst falsy value, or last truthy value
||Logical ORLeft is truthyFirst truthy value, or last falsy value
??Nullish CoalescingLeft is not null/undefinedLeft if not null/undefined, else right
!Logical NOTN/A (unary)Always boolean (inverts truthiness)
!!Double NOTN/A (unary)Coerces to boolean (no inversion)
logical.js
JavaScript
1// Logical AND (&&) — returns first falsy OR last truthy
2console.log(true && "hello"); // "hello" (both truthy → last value)
3console.log(1 && 2 && 3); // 3 (all truthy → last)
4console.log(0 && "hello"); // 0 (first falsy)
5console.log(null && "world"); // null (first falsy)
6
7// Common pattern: guard operator
8const user = { name: "Alice" };
9user && console.log(user.name); // "Alice" (only runs if user exists)
10
11// Logical OR (||) — returns first truthy OR last falsy
12console.log(null || "default"); // "default" (null is falsy)
13console.log(0 || 42); // 42 (0 is falsy)
14console.log("" || "fallback"); // "fallback"
15console.log("hello" || "world"); // "hello" (first truthy — short-circuits)
16
17// Common pattern: default values (but watch out for falsy!)
18const count = 0;
19console.log(count || 10); // 10 — 0 is falsy, so gets default!
20console.log(count ?? 10); // 0 — only null/undefined trigger default
21
22// Short-circuit evaluation in action
23function expensive() {
24 console.log("expensive called");
25 return true;
26}
27
28false && expensive(); // expensive() NEVER called — short-circuits
29true || expensive(); // expensive() NEVER called — short-circuits
30
31// Chaining logical operators
32const name = user && user.profile && user.profile.name || "Anonymous";
33// Better with optional chaining:
34const name2 = user?.profile?.name ?? "Anonymous";
35
36// Logical NOT (!) — always returns boolean
37console.log(!true); // false
38console.log(!0); // true
39console.log(!""); // true
40console.log(!{}); // false (object is truthy)
41
42// Double NOT (!!) — coerces to boolean
43console.log(!!1); // true
44console.log(!!0); // false
45console.log(!!""); // false
46console.log(!!"hello"); // true

Logical Assignment Operators (ES2021)

logical-assignment.js
JavaScript
1// Logical assignment — combine logical operators with assignment
2// ES2021 — supported in all modern environments
3
4let a = 0;
5a ||= 10; // a = a || 10 — assigns if a is falsy
6console.log(a); // 10
7
8let b = 1;
9b &&= 10; // b = b && 10 — assigns if a is truthy
10console.log(b); // 10
11
12let c = null;
13c ??= 10; // c = c ?? 10 — assigns if a is null/undefined
14console.log(c); // 10
15
16// Practical examples
17let config = {};
18config.timeout ??= 3000; // only set if missing
19config.retries ??= 3;
20
21let userSettings = {};
22userSettings.theme ||= "light"; // set default
23
24let isValid = true;
25isValid &&= validateForm(); // only validate if already valid

best practice

Understand the difference between || and ??. The || operator treats all falsy values (0, "", false, null, undefined, NaN) as false. The ?? operator only treats null and undefined as false. Use ?? for default values when 0 or "" are valid inputs.
Nullish Coalescing & Optional Chaining

Introduced in ES2020, the nullish coalescing operator (??) and optional chaining operator (?.) together eliminate an entire category of null-checking boilerplate. They are among the most impactful modern JavaScript features.

nullish-optional.js
JavaScript
1// Nullish coalescing (??) — default only for null/undefined
2const value = 0;
3console.log(value || 10); // 10 (0 is falsy — unexpected default)
4console.log(value ?? 10); // 0 (0 is not null/undefined — correct)
5
6const empty = "";
7console.log(empty || "fallback"); // "fallback"
8console.log(empty ?? "fallback"); // ""
9
10const isFalse = false;
11console.log(isFalse || true); // true
12console.log(isFalse ?? true); // false
13
14// ?? cannot be chained with && or || without parentheses
15// console.log(null || undefined ?? "default"); // SyntaxError
16console.log((null || undefined) ?? "default"); // "default"
17console.log(null || (undefined ?? "default")); // "default"
18
19// Optional chaining (?.) — safe property access
20const data = {
21 user: {
22 // address is missing
23 },
24};
25
26// Without optional chaining — verbose
27const city = data && data.user && data.user.address && data.user.address.city;
28
29// With optional chaining — clean
30const city2 = data?.user?.address?.city;
31console.log(city2); // undefined — no error
32
33// Optional method call
34const result = obj?.method?.();
35console.log(data?.nonexistent?.()); // undefined
36
37// Optional dynamic property access
38const key = "address";
39console.log(data?.user?.[key]?.city);
40
41// Optional chaining with delete
42delete data?.user?.address;
43
44// Short-circuiting — stops at first null/undefined
45data?.user?.profile?.name?.toUpperCase();
46// If data, user, profile, or name is null/undefined → undefined
🔥

pro tip

Optional chaining and nullish coalescing work together perfectly: data?.user?.name ?? "Anonymous". The first part safely accesses deeply nested properties, and the second provides a fallback if the result is null or undefined. This pattern replaces the vast majority of manual null checks.
Bitwise Operators

Bitwise operators treat operands as 32-bit signed integers and perform operations at the binary level. They are rarely used in everyday JavaScript but are essential for low-level programming, flags/enums, performance-critical binary operations, and certain algorithms.

OperatorNameExampleBinary Result
&AND5 & 31 (0101 & 0011 = 0001)
|OR5 | 37 (0101 | 0011 = 0111)
^XOR5 ^ 36 (0101 ^ 0011 = 0110)
~NOT~5-6 (inverts all bits)
<<Left shift5 << 110 (0101 → 1010)
>>Sign-propagating right shift-5 >> 1-3 (preserves sign)
>>>Zero-fill right shift-5 >>> 12147483645 (unsigned)
bitwise.js
JavaScript
1// Bitwise AND, OR, XOR
2console.log(5 & 3); // 1 (0101 & 0011 = 0001)
3console.log(5 | 3); // 7 (0101 | 0011 = 0111)
4console.log(5 ^ 3); // 6 (0101 ^ 0011 = 0110)
5console.log(~5); // -6 (inverts all bits)
6
7// Bitwise shifts
8console.log(5 << 1); // 10 (multiply by 2)
9console.log(5 << 2); // 20 (multiply by 4)
10console.log(16 >> 1); // 8 (divide by 2)
11console.log(16 >> 2); // 4 (divide by 4)
12console.log(-16 >> 1); // -8 (sign-preserving)
13
14// Practical: flag/enum pattern
15const PERMISSION_READ = 1; // 001
16const PERMISSION_WRITE = 2; // 010
17const PERMISSION_EXEC = 4; // 100
18
19let permissions = PERMISSION_READ | PERMISSION_WRITE; // 011
20console.log(permissions & PERMISSION_READ); // 1 (has read)
21console.log(permissions & PERMISSION_EXEC); // 0 (no exec)
22
23// Toggle a flag
24permissions ^= PERMISSION_WRITE; // remove write
25console.log(permissions & PERMISSION_WRITE); // 0
26
27// Practical: double bitwise NOT for fast floor
28console.log(~~3.14); // 3 (faster than Math.floor for positives)
29console.log(~~-3.14); // -3 (Math.floor would give -4!)
30console.log(Math.floor(-3.14)); // -4
31
32// Practical: check if number is odd/even
33console.log(5 & 1); // 1 (odd)
34console.log(4 & 1); // 0 (even)
35
36// Note: bitwise operators work on 32-bit signed integers
37// Numbers are converted to 32-bit, operated on, then converted back
38console.log(~0); // -1
39console.log(~-1); // 0
Special Operators

typeof, void, delete, instanceof, in

special-operators.js
JavaScript
1// typeof — returns type as string
2console.log(typeof 42); // "number"
3console.log(typeof "hello"); // "string"
4console.log(typeof true); // "boolean"
5console.log(typeof undefined); // "undefined"
6console.log(typeof null); // "object" (legacy bug)
7console.log(typeof Symbol()); // "symbol"
8console.log(typeof 42n); // "bigint"
9console.log(typeof function(){}); // "function"
10
11// void — evaluates expression and returns undefined
12console.log(void 0); // undefined
13console.log(void(42)); // undefined
14// Used to prevent default actions in HTML:
15// <a href="javascript:void(0)">Click</a>
16// In frameworks: prevents navigation for click handlers
17
18// delete — removes property from object
19const obj = { a: 1, b: 2, c: 3 };
20delete obj.b;
21console.log(obj); // { a: 1, c: 3 }
22
23delete obj.a;
24console.log(obj); // { c: 3 }
25
26// delete returns true if successful
27console.log(delete obj.c); // true
28console.log(delete obj.nonexistent); // true
29
30// Cannot delete local variables, functions, or built-in objects
31let x = 5;
32console.log(delete x); // false (in strict mode: TypeError)
33
34// delete only works on object properties
35delete obj.__proto__; // cannot delete inherited properties
36
37// instanceof — checks prototype chain
38console.log([] instanceof Array); // true
39console.log({} instanceof Object); // true
40console.log(new Date() instanceof Date); // true
41class Animal {}
42const dog = new Animal();
43console.log(dog instanceof Animal); // true
44console.log(dog instanceof Object); // true
45
46// in — checks if property exists in object (including prototype)
47console.log("toString" in {}); // true (inherited)
48console.log("a" in {a:1}); // true
49console.log("b" in {a:1}); // false
50
51// in with arrays — checks index, not value
52const arr = [10, 20, 30];
53console.log(0 in arr); // true (index 0 exists)
54console.log(3 in arr); // false (index 3 doesn't exist)
55console.log("length" in arr); // true

Comma Operator

The comma operator evaluates each of its operands left to right and returns the value of the last operand. It is rarely necessary but occasionally useful in specific patterns.

comma-operator.js
JavaScript
1// Comma operator — evaluates both, returns last
2const result = (1, 2, 3);
3console.log(result); // 3
4
5// Side effects with comma
6let a = 0, b = 0;
7const val = (a++, b++, a + b);
8console.log(val); // 2 (a=1, b=1, 1+1=2)
9
10// Practical: for loop with multiple counters
11for (let i = 0, j = 10; i < j; i++, j--) {
12 console.log(i, j);
13}
14
15// Practical: arrow function with side effects
16const getNextId = (() => {
17 let id = 0;
18 return () => (id++, id);
19})();
20console.log(getNextId()); // 1
21console.log(getNextId()); // 2
22
23// Comma in ternary (use sparingly — often hurts readability)
24const x = condition
25 ? (doThis(), doThat(), "done")
26 : (fallbackThis(), "fallback");

Grouping Operator

grouping.js
JavaScript
1// Parentheses — control evaluation order
2console.log(2 + 3 * 4); // 14 (multiplication first)
3console.log((2 + 3) * 4); // 20 (addition first)
4
5// Overriding precedence
6const average = (a + b + c) / 3;
7
8// IIFE — parentheses around function expression
9(function() {
10 console.log("IIFE");
11})();
12
13// Readability — clarify complex expressions
14const isValid = (age >= 18) && (hasLicense || isStudent);
15// Without parens: same result but harder to parse
Operator Precedence

Operator precedence determines the order in which operators are evaluated in expressions. Operators with higher precedence are evaluated first. When operators have the same precedence, associativity (left-to-right or right-to-left) determines the evaluation order.

PrecedenceOperatorAssociativity
19() Groupingn/a
18. [] ?.()left-to-right
17new (with args) .n/a
16! ~ + - typeof void deleteright-to-left
15**right-to-left
14* / %left-to-right
13+ -left-to-right
12<< >> >>>left-to-right
11< <= > >= in instanceofleft-to-right
10== != === !==left-to-right
9&left-to-right
8^left-to-right
7|left-to-right
6&&left-to-right
5||left-to-right
4??right-to-left
3? : (ternary)right-to-left
2= += -= **= ||= ??= etc.right-to-left
1, (comma)left-to-right
precedence.js
JavaScript
1// Understanding precedence with real examples
2console.log(2 + 3 * 4); // 14 (* has higher precedence than +)
3console.log((2 + 3) * 4); // 20 (() overrides precedence)
4
5// Chaining with same precedence (left-to-right)
6console.log(16 / 4 / 2); // 2 (left-to-right: (16/4)/2 = 2)
7console.log(16 / (4 / 2)); // 8 (parentheses change it)
8
9// Assignment is right-to-left
10let a, b, c;
11a = b = c = 5;
12// Evaluated as: a = (b = (c = 5))
13console.log(a, b, c); // 5, 5, 5
14
15// Exponentiation is right-to-left
16console.log(2 ** 3 ** 2); // 512 (2^(3^2) = 2^9 = 512)
17console.log((2 ** 3) ** 2); // 64 ((2^3)^2 = 8^2 = 64)
18
19// Logical operators — && before ||
20console.log(true || false && false);
21// Evaluated as: true || (false && false) → true || false → true
22
23// ?? cannot directly mix with && or ||
24// console.log(null && undefined ?? "default"); // SyntaxError
25console.log(null && (undefined ?? "default")); // null (&& short-circuits)
26
27// Ternary is right-to-left (nested ternary — avoid for readability)
28const grade = score >= 90 ? "A" : score >= 80 ? "B" : "C";
29// Evaluated as: score >= 90 ? "A" : (score >= 80 ? "B" : "C")
30
31// Best practice: use parentheses for clarity
32const result = (a + b) * (c - d) / (e || 1);

best practice

You do not need to memorize the precedence table. When in doubt, use parentheses (). They cost nothing in readability and ensure your intent is clear. Complex expressions without parentheses are a maintenance burden — your future self and colleagues will thank you.
Assignment Operators
assignment.js
JavaScript
1// Basic assignment
2let x = 10;
3
4// Compound assignment — applies operator then assigns
5x += 5; // x = x + 5 → 15
6x -= 3; // x = x - 3 → 12
7x *= 2; // x = x * 2 → 24
8x /= 4; // x = x / 4 → 6
9x %= 2; // x = x % 2 → 0
10x **= 3; // x = x ** 3 → 0 (0**3 = 0)
11x = 5;
12x <<= 1; // x = x << 1 → 10
13x >>= 1; // x = x >> 1 → 5
14x >>>= 1; // x = x >>> 1 → 2
15x &= 3; // x = x & 3 → 1
16x |= 2; // x = x | 2 → 3
17x ^= 1; // x = x ^ 1 → 2
18
19// Logical assignment (ES2021)
20let a = null;
21a ??= "default"; // assigns only if null/undefined
22
23let b = 0;
24b ||= 10; // assigns only if falsy
25
26let c = true;
27c &&= "set"; // assigns only if truthy
28
29// Destructuring assignment
30const [first, second] = [10, 20];
31const { name, age } = { name: "Alice", age: 30 };
Operator Best Practices
Always use === and !== — never use == or != which coerce types silently
Use ?? for default values instead of || when 0 or empty string are valid values
Use optional chaining ?. for safe deep property access — replace nested && checks
Use parentheses to clarify complex expressions — readability over cleverness
Avoid nested ternaries — they are hard to read. Use if/else or switch instead
Use unary + for explicit numeric conversion — it's concise and clear
Understand short-circuit evaluation — && runs the right side only if left is truthy
Prefer logical assignment (||=, &&=, ??=) for cleaner conditional assignment
Avoid the comma operator in most code — it usually hurts readability
Use ~~ only when you specifically need 32-bit integer truncation, not Math.floor()
🔥

pro tip

The most common operator mistake in JavaScript: using = instead of === in conditions (e.g., if (x = 5) instead of if (x === 5)). Use a linter (ESLint with no-cond-assign) to catch this automatically. This one rule prevents more bugs than almost any other.
$Blueprint — Engineering Documentation·Section ID: JS-OPS·Revision: 1.0