JavaScript operators optional chaining nullish coalescing guide 2026

JavaScript Operators: Arithmetic, Comparison, Logical, Bitwise & More

Operators are the verbs of programming — they perform actions on values. JavaScript has more operators than most languages, and understanding them all (including the newer ones like ?? and ?.) is essential for writing clean, modern code. This guide covers every operator you will encounter.

Arithmetic operators

// Basic arithmetic
10 + 3     // 13 (addition)
10 - 3     // 7  (subtraction)
10 * 3     // 30 (multiplication)
10 / 3     // 3.3333... (division — always floating point)
10 % 3     // 1  (remainder/modulo)
10 ** 3    // 1000 (exponentiation — ES2016)

// Unary operators
let x = 5;
+x         // 5  (unary plus — converts to number)
-x         // -5 (unary negation)
+'42'      // 42 (string to number conversion)
+true      // 1
+false     // 0
+null      // 0
+undefined // NaN

// Increment and decrement
let a = 5;
a++        // returns 5, then a becomes 6 (post-increment)
++a        // a becomes 7, then returns 7 (pre-increment)
a--        // returns 7, then a becomes 6 (post-decrement)
--a        // a becomes 5, then returns 5 (pre-decrement)

// The difference between pre and post matters:
let i = 0;
const arr = [10, 20, 30];
console.log(arr[i++]);  // 10 (uses i=0, then increments to 1)
console.log(arr[++i]);  // 30 (increments to 2, then uses i=2)

// String concatenation with +
'Hello' + ' ' + 'World'  // 'Hello World'
'Age: ' + 25              // 'Age: 25' (number coerced to string)
5 + 3 + ' apples'         // '8 apples' (left-to-right: 5+3=8, then string)
'apples: ' + 5 + 3        // 'apples: 53' (left-to-right: string concat)

Assignment operators

// Basic assignment
let x = 10;

// Compound assignment — shorthand for x = x op value
x += 5;    // x = x + 5  → 15
x -= 3;    // x = x - 3  → 12
x *= 2;    // x = x * 2  → 24
x /= 4;    // x = x / 4  → 6
x %= 4;    // x = x % 4  → 2
x **= 3;   // x = x ** 3 → 8

// Logical assignment (ES2021) — incredibly useful!
// These only assign if the condition is met

// ||= assigns if the left side is falsy
let name = '';
name ||= 'Anonymous';  // name is '' (falsy), so assign 'Anonymous'
// Equivalent to: name = name || 'Anonymous'

// &&= assigns if the left side is truthy
let user = { name: 'Ada' };
user &&= { ...user, role: 'admin' };  // user is truthy, so assign
// Equivalent to: user = user && { ...user, role: 'admin' }

// ??= assigns if the left side is null or undefined
let port = undefined;
port ??= 3000;  // port is undefined, so assign 3000
let zero = 0;
zero ??= 5;     // zero is 0 (not null/undefined), so NO assignment

// Bitwise assignment
x &= 0xFF;     // x = x & 0xFF
x |= 0x100;    // x = x | 0x100
x ^= 0xFF;     // x = x ^ 0xFF
x <<= 2;       // x = x << 2
x >>= 1;       // x = x >> 1

// Destructuring assignment
const [a, b] = [1, 2];          // a=1, b=2
const { name: n, age } = { name: 'Ada', age: 30 };  // n='Ada', age=30
[a, b] = [b, a];                 // swap values!

Comparison operators

// Strict equality (always prefer these!)
5 === 5        // true
5 === '5'      // false (different types)
null === undefined  // false
NaN === NaN    // false (NaN is never equal to itself)

// Strict inequality
5 !== '5'      // true
5 !== 5        // false

// Loose equality (avoid! performs type coercion)
5 == '5'       // true  ('5' coerced to 5)
0 == false     // true  (false coerced to 0)
'' == false    // true  (both coerced to 0)
null == undefined  // true (special rule)
null == 0      // false (another special rule)

// Relational operators
5 > 3         // true
5 >= 5        // true
3 < 5         // true
5 <= 5        // true

// String comparison (lexicographic, by Unicode code point)
'apple' < 'banana'    // true
'Zebra' < 'apple'     // true (uppercase letters have lower code points!)
'10' < '9'            // true (comparing as strings: '1' < '9')
'10' < 9              // false (string coerced to number: 10 > 9)

// Object.is() — the most precise equality
Object.is(NaN, NaN)   // true  (fixes NaN comparison)
Object.is(0, -0)      // false (distinguishes +0 and -0)
Object.is(5, 5)       // true

Logical operators

JavaScript’s logical operators do not always return booleans — they return one of their operands. This makes them powerful for control flow and default values.

// && (AND) — returns the first falsy value, or the last value
true && true       // true
true && false      // false
false && true      // false (short-circuits, never evaluates right side)
'hello' && 42      // 42 (both truthy, returns last)
0 && 'hello'       // 0 (first is falsy, returns it)
null && undefined   // null (first is falsy)

// Practical: conditional execution
user && user.greet()           // only calls greet() if user exists
isAdmin && showAdminPanel()    // only shows panel if admin

// || (OR) — returns the first truthy value, or the last value
true || false      // true
false || true      // true
false || false     // false
'' || 'default'    // 'default' ('' is falsy)
0 || 42            // 42 (0 is falsy)
null || undefined  // undefined (both falsy, returns last)

// Practical: default values
const name = userInput || 'Anonymous';
const port = config.port || 3000;

// ! (NOT) — returns the boolean opposite
!true              // false
!false             // true
!0                 // true (0 is falsy)
!'hello'           // false ('hello' is truthy)
!null              // true (null is falsy)

// !! (double NOT) — converts to boolean
!!0                // false
!!'hello'          // true
!!null             // false
!![]               // true (empty array is truthy!)

Nullish coalescing operator (??)

The ?? operator was added in ES2020 to solve a specific problem with ||: it only treats null and undefined as “missing” values, not 0, '', or false.

// || treats 0, '', and false as "missing"
0 || 10         // 10 (0 is falsy — probably not what you want!)
'' || 'default' // 'default' ('' is falsy)
false || true   // true

// ?? only treats null and undefined as "missing"
0 ?? 10         // 0 (0 is not null/undefined)
'' ?? 'default' // '' ('' is not null/undefined)
false ?? true   // false
null ?? 10      // 10
undefined ?? 10 // 10

// Real-world example: user settings
const config = { volume: 0, darkMode: false, username: '' };

// With || — WRONG (overrides valid values)
const volume = config.volume || 50;       // 50 (wrong! volume should be 0)
const darkMode = config.darkMode || true; // true (wrong! should be false)
const username = config.username || 'Guest'; // 'Guest' (wrong! empty string is valid)

// With ?? — CORRECT
const volume2 = config.volume ?? 50;       // 0 (correct!)
const darkMode2 = config.darkMode ?? true; // false (correct!)
const username2 = config.username ?? 'Guest'; // '' (correct!)

// Cannot mix ?? with && or || without parentheses
// null ?? true || false  // SyntaxError!
(null ?? true) || false   // true (OK with parentheses)
null ?? (true || false)   // true (OK with parentheses)

Optional chaining (?.)

The ?. operator lets you safely access deeply nested properties without checking each level for null or undefined.

const user = {
  name: 'Ada',
  address: {
    city: 'London',
    zip: 'SW1A'
  },
  getRole() { return 'admin'; }
};

// Without optional chaining — verbose null checks
const city = user && user.address && user.address.city;

// With optional chaining — clean and safe
const city2 = user?.address?.city;     // 'London'
const zip = user?.address?.zip;        // 'SW1A'
const country = user?.address?.country; // undefined (no error)

// Works with deeply nested objects
const value = obj?.level1?.level2?.level3?.target;
// Returns undefined at any point if the chain breaks

// Method calls
user?.getRole()         // 'admin'
user?.nonExistent()     // undefined (does not throw!)

// Array access
const arr = [1, 2, 3];
arr?.[0]               // 1
arr?.[99]              // undefined
null?.[0]              // undefined (no error)

// Combined with ??
const name = user?.name ?? 'Unknown';
const theme = user?.preferences?.theme ?? 'light';

// Dynamic property access
const prop = 'name';
user?.[prop]           // 'Ada'

// Practical: DOM access
const text = document.querySelector('.title')?.textContent ?? 'No title';
const href = document.querySelector('a')?.getAttribute('href');

Ternary operator

// condition ? valueIfTrue : valueIfFalse
const status = age >= 18 ? 'adult' : 'minor';
const greeting = isLoggedIn ? 'Welcome back!' : 'Please sign in';

// Can be used inline
console.log(\`You have \${count} item\${count !== 1 ? 's' : ''}\`);

// Nested ternary (use sparingly — can be hard to read)
const grade = score >= 90 ? 'A'
            : score >= 80 ? 'B'
            : score >= 70 ? 'C'
            : score >= 60 ? 'D'
            : 'F';

// Often cleaner as a function or object lookup
const grades = { A: 90, B: 80, C: 70, D: 60 };
function getGrade(score) {
  for (const [grade, min] of Object.entries(grades)) {
    if (score >= min) return grade;
  }
  return 'F';
}

Bitwise operators

Bitwise operators work on the binary representation of 32-bit integers. They are rarely needed in web development but are essential for flags, permissions, colors, and low-level operations.

// & (AND) — both bits must be 1
0b1100 & 0b1010  // 0b1000 = 8

// | (OR) — either bit must be 1
0b1100 | 0b1010  // 0b1110 = 14

// ^ (XOR) — bits must be different
0b1100 ^ 0b1010  // 0b0110 = 6

// ~ (NOT) — flip all bits
~5               // -6 (flips sign and subtracts 1)
~~3.7            // 3 (double NOT truncates decimals — faster than Math.floor for positives)

// << (left shift) — multiply by 2^n
5 << 1           // 10 (5 * 2)
5 << 3           // 40 (5 * 8)

// >> (right shift) — divide by 2^n (preserves sign)
20 >> 2          // 5 (20 / 4)
-20 >> 2         // -5

// >>> (unsigned right shift) — fills with zeros
-1 >>> 0         // 4294967295 (all 32 bits set)

// Practical: permission flags
const READ = 0b001;    // 1
const WRITE = 0b010;   // 2
const EXECUTE = 0b100; // 4

let perms = READ | WRITE;       // 0b011 = 3
perms & READ                     // 1 (truthy: has READ)
perms & EXECUTE                  // 0 (falsy: no EXECUTE)
perms |= EXECUTE;                // add EXECUTE: 0b111 = 7
perms &= ~WRITE;                 // remove WRITE: 0b101 = 5

// Practical: RGB color manipulation
const color = 0xFF6B35;
const red = (color >> 16) & 0xFF;   // 255
const green = (color >> 8) & 0xFF;  // 107
const blue = color & 0xFF;           // 53

// Swap without temp variable
let a = 5, b = 10;
a ^= b; b ^= a; a ^= b;  // a=10, b=5

Comma operator

// Evaluates all expressions, returns the last one
const x = (1, 2, 3);  // x = 3

// Most common in for loops
for (let i = 0, j = 10; i < j; i++, j--) {
  console.log(i, j);  // 0 10, 1 9, 2 8, 3 7, 4 6
}

typeof and instanceof

// typeof — returns a string describing the type
typeof 'hello'        // 'string'
typeof 42             // 'number'
typeof true           // 'boolean'
typeof undefined      // 'undefined'
typeof null           // 'object' (historic bug!)
typeof {}             // 'object'
typeof []             // 'object' (arrays are objects!)
typeof function(){}   // 'function'
typeof Symbol()       // 'symbol'
typeof 42n            // 'bigint'

// typeof with undeclared variables (safe — no ReferenceError)
typeof nonExistent    // 'undefined'
// nonExistent        // ReferenceError!

// instanceof — checks prototype chain
[] instanceof Array           // true
[] instanceof Object          // true (Array extends Object)
new Date() instanceof Date    // true
'hello' instanceof String     // false (primitive, not String object)
new String('hello') instanceof String  // true (wrapper object)

// in — checks if property exists in object
'name' in { name: 'Ada' }    // true
'age' in { name: 'Ada' }     // false
0 in ['a', 'b']              // true (index 0 exists)
'length' in []                // true (arrays have .length)

Operator precedence

When multiple operators appear in one expression, precedence determines which executes first. Here are the most important ones, from highest to lowest:

// Precedence (highest to lowest):
// 1.  () — grouping
// 2.  ?.  — optional chaining
// 3.  ++ -- — increment/decrement
// 4.  ! ~ + - typeof void delete — unary operators
// 5.  ** — exponentiation (right-to-left!)
// 6.  * / % — multiplication, division, remainder
// 7.  + - — addition, subtraction
// 8.  << >> >>> — bitwise shift
// 9.  < > <= >= in instanceof — relational
// 10. == != === !== — equality
// 11. & — bitwise AND
// 12. ^ — bitwise XOR
// 13. | — bitwise OR
// 14. && — logical AND
// 15. || — logical OR
// 16. ?? — nullish coalescing
// 17. ?: — ternary
// 18. = += -= etc. — assignment (right-to-left!)
// 19. , — comma

// Examples of precedence in action:
2 + 3 * 4          // 14 (not 20: * before +)
2 ** 3 ** 2        // 512 (not 64: ** is right-to-left: 3**2=9, 2**9=512)
true || false && false  // true (&& before ||)
a = b = c = 5      // all 5 (= is right-to-left)

// When in doubt, use parentheses!
(2 + 3) * 4        // 20 (explicit grouping)

Operators are the core tools for manipulating data in JavaScript. The modern additions — nullish coalescing (??), optional chaining (?.), and logical assignment (||=, &&=, ??=) — solve real problems that used to require verbose workarounds. Master all of them and your code will be cleaner, safer, and more expressive.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *