Operators

โ–ถ Try It Yourself

Operators are the verbs of JavaScript โ€” they perform actions on values and variables. JavaScript has a rich set of operators covering arithmetic, comparison, logical operations, and modern shorthand patterns. Mastering operators โ€” especially the critical difference between == and ===, and the nullish coalescing and optional chaining operators โ€” is fundamental to writing correct, expressive JavaScript code.

Operator Categories

Category Operators Example
Arithmetic + - * / % ** 10 % 3 โ†’ 1 | 2 ** 8 โ†’ 256
Assignment = += -= *= /= ??= x += 5 shorthand for x = x + 5
Comparison == === != !== > < >= <= '5' === 5 โ†’ false (strict)
Logical && || ! ?? null ?? 'default' โ†’ ‘default’
Ternary condition ? a : b age >= 18 ? 'adult' : 'minor'
Optional chaining ?. user?.address?.city
Spread / Rest ... [...arr1, ...arr2]

== vs === (Loose vs Strict Equality)

Expression == (loose) === (strict)
'5' vs 5 true โ€” coerces string to number false โ€” different types
0 vs false true โ€” coerces false to 0 false โ€” different types
null vs undefined true โ€” special rule false โ€” different types
0 vs '' true โ€” both coerce to 0 false โ€” different types
1 vs '1' true false
null vs null true true

Modern Logical Operators (ES2020)

Operator Name Returns Use For
a ?? b Nullish coalescing b if a is null/undefined; otherwise a Default values โ€” safer than ||
a || b Logical OR b if a is falsy; otherwise a Default values โ€” but triggers on 0, ”, false
a && b Logical AND a if a is falsy; otherwise b Short-circuit: run b only if a is truthy
a?.b Optional chaining undefined if a is null/undefined; otherwise a.b Safe property access on possibly-null objects
a ??= b Nullish assignment Assigns b to a only if a is null/undefined Initialise variables with defaults
Note: Always use === (strict equality) instead of == (loose equality) in production JavaScript. The loose equality operator applies a complex set of type coercion rules that are difficult to memorise and produce surprising results. The only common exception is value == null, which is a convenient shorthand that catches both null and undefined in one check.
Tip: Prefer ?? over || for default values when 0, false, or '' are valid values. Example: const port = config.port || 3000 would use 3000 even if config.port is intentionally 0. const port = config.port ?? 3000 only falls back to 3000 if port is null or undefined โ€” preserving the intended zero.
Warning: The + operator is overloaded โ€” it both adds numbers AND concatenates strings. If either operand is a string, + concatenates: '5' + 3 produces '53', not 8. This is one of JavaScript’s most common sources of bugs, especially when reading values from HTML inputs (which always return strings). Always convert to number first with Number(), parseInt(), or the unary + operator.

Basic Example

// โ”€โ”€ Arithmetic operators โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
console.log(10 + 3);    // 13
console.log(10 - 3);    // 7
console.log(10 * 3);    // 30
console.log(10 / 3);    // 3.3333...
console.log(10 % 3);    // 1  (remainder / modulo)
console.log(2  ** 10);  // 1024 (exponentiation)

// Increment / decrement
let count = 5;
count++;       // count = 6  (post-increment)
count--;       // count = 5  (post-decrement)
++count;       // count = 6  (pre-increment โ€” returns 6)

// โ”€โ”€ String + number coercion trap โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
console.log('5' + 3);      // '53'  โ† string concatenation!
console.log('5' - 3);      // 2     โ† subtraction coerces string to number
console.log(Number('5') + 3); // 8  โ† explicit conversion: safe
console.log(+'5' + 3);        // 8  โ† unary + converts to number

// โ”€โ”€ Comparison โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
console.log(5 === 5);       // true
console.log(5 === '5');     // false โ€” different types
console.log(5 ==  '5');     // true  โ€” loose (avoid!)
console.log(null == undefined);  // true  (one valid use of ==)
console.log(null === undefined); // false

// โ”€โ”€ Logical operators โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const user = null;
const name = user && user.name;  // null  โ€” short-circuits when user is falsy
console.log(name);

const username = user?.name ?? 'Guest';  // 'Guest' โ€” optional chain + nullish
console.log(username);

// || vs ?? for defaults
const config = { port: 0, debug: false, title: '' };
console.log(config.port  || 3000);   // 3000  โ† WRONG โ€” 0 is falsy!
console.log(config.port  ?? 3000);   // 0     โ† CORRECT โ€” 0 is not null/undefined
console.log(config.debug || true);   // true  โ† WRONG โ€” false is falsy!
console.log(config.debug ?? true);   // false โ† CORRECT

// โ”€โ”€ Ternary operator โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const age   = 20;
const label = age >= 18 ? 'adult' : 'minor';
console.log(label);   // 'adult'

// Nested ternary (use sparingly โ€” prefer if/else for readability)
const score = 75;
const grade = score >= 90 ? 'A'
            : score >= 80 ? 'B'
            : score >= 70 ? 'C'
            : score >= 60 ? 'D' : 'F';
console.log(grade);   // 'C'

// โ”€โ”€ Optional chaining โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const response = {
  data: {
    user: {
      profile: { avatar: 'img.jpg' }
    }
  }
};

// Without optional chaining โ€” crashes if any level is undefined
// const avatar = response.data.user.profile.avatar;

// With optional chaining โ€” returns undefined if any level is missing
const avatar  = response?.data?.user?.profile?.avatar;
const missing = response?.data?.orders?.[0]?.id;  // undefined โ€” orders doesn't exist
console.log(avatar);   // 'img.jpg'
console.log(missing);  // undefined โ€” no error

How It Works

Step 1 โ€” The + Operator Checks Types First

When JavaScript sees +, it checks both operands. If either is a string, it converts the other to a string and concatenates. If both are numbers, it adds them. This is why '5' + 3 gives '53' โ€” the number 3 is converted to the string '3' and appended.

Step 2 โ€” && and || Return Values, Not Booleans

&& returns the first falsy value it finds, or the last value if all are truthy. || returns the first truthy value, or the last value if all are falsy. This is called short-circuit evaluation โ€” and it means user && user.name returns user if user is falsy, or user.name if user is truthy.

Step 3 โ€” ?? Only Triggers on null/undefined

Nullish coalescing ?? checks strictly for null or undefined โ€” not all falsy values. This makes it the correct tool for default values when 0, false, or '' are meaningful values that should be preserved.

Step 4 โ€” Optional Chaining Prevents Crashes

a?.b first checks if a is null or undefined. If so, it returns undefined without throwing an error. If a has a value, it returns a.b normally. This short-circuits the entire chain โ€” a?.b?.c?.d returns undefined at the first null link.

Step 5 โ€” Strict Equality Compares Type and Value

=== performs no type coercion โ€” it checks both the type and value. 5 === '5' is false because one is a number and the other is a string. This predictable behaviour is why strict equality is the default choice in modern JavaScript.

Real-World Example: Config Parser with Safe Defaults

// config-parser.js

function parseConfig(userConfig) {
  // ?? preserves 0, false, and '' as valid values
  const port    = userConfig?.port    ?? 3000;
  const debug   = userConfig?.debug   ?? false;
  const timeout = userConfig?.timeout ?? 5000;
  const host    = userConfig?.host    ?? 'localhost';
  const prefix  = userConfig?.apiPrefix ?? '/api/v1';

  // Validate port is a valid number
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
    throw new RangeError(`Invalid port: ${port}. Must be 1โ€“65535`);
  }

  // Short-circuit: only log in debug mode
  debug && console.log('[Config]', { port, host, prefix, timeout });

  return { port, debug, timeout, host, prefix };
}

// Tests
console.log(parseConfig({}));                         // all defaults
console.log(parseConfig({ port: 8080, debug: true })); // custom port
console.log(parseConfig({ port: 0 }));                // port 0 โ†’ RangeError
console.log(parseConfig(null));                       // all defaults (null safe)
console.log(parseConfig({ timeout: 0 }));             // timeout 0 preserved via ??

Common Mistakes

Mistake 1 โ€” Using == instead of ===

โŒ Wrong โ€” loose equality causes unexpected matches:

if (userInput == 0) { /* runs for '', false, null, undefined too! */ }

โœ… Correct โ€” strict equality checks type and value:

if (userInput === 0) { /* only runs when userInput is exactly the number 0 */ }

Mistake 2 โ€” Using || for defaults when 0 or false are valid

โŒ Wrong โ€” || replaces 0, false, and ” with the default:

const volume = settings.volume || 50;   // volume is 0? Gets replaced with 50!

โœ… Correct โ€” ?? only replaces null and undefined:

const volume = settings.volume ?? 50;   // 0 is preserved; only null/undefined โ†’ 50

Mistake 3 โ€” Deeply accessing properties without optional chaining

โŒ Wrong โ€” throws TypeError if any intermediate value is null/undefined:

const city = user.address.city;   // TypeError if address is undefined

โœ… Correct โ€” optional chaining returns undefined safely:

const city = user?.address?.city ?? 'Unknown';

▶ Try It Yourself

Quick Reference

Operator Description Example
=== / !== Strict equality / inequality Always use over == / !=
?? Nullish coalescing โ€” default if null/undefined val ?? 'default'
?. Optional chaining โ€” safe property access obj?.prop?.sub
&& AND โ€” returns first falsy or last value isAdmin && renderAdminPanel()
|| OR โ€” returns first truthy or last value name || 'Anonymous' (careful with 0/false)
? : Ternary โ€” inline if/else x > 0 ? 'pos' : 'neg'
** Exponentiation 2 ** 8 โ†’ 256
% Modulo (remainder) 7 % 3 โ†’ 1 (odd/even: n % 2)

🧠 Test Yourself

Given const x = { count: 0 }, what does x.count || 10 return?





โ–ถ Try It Yourself