webdev.complete
💛 JavaScript Foundations
JavaScript
Lesson 25 of 117
20 min

Operators & Coercion

==, ===, ??, ?., and the floating-point gotcha.

Operators are the punctuation of JavaScript. They look harmless, but a few of them have famously weird behavior because JS will quietly convert types behind your back. This lesson teaches the operators you'll use every day and the coercion rules that trip up everyone exactly once.

Arithmetic operators

The boring math ones first, because there are still surprises:

js
5 + 3        // 8
10 - 4       // 6
3 * 4        // 12
10 / 3       // 3.3333333333333335
10 % 3       // 1   (remainder)
2 ** 10      // 1024 (exponent)

let count = 0;
count++;     // post-increment (returns old, then adds)
++count;     // pre-increment  (adds, then returns)

The + operator does double duty: it adds numbers and it glues strings together. This is where coercion starts to bite.

Comparison: == vs ===

JS has two equality operators. They look almost the same but behave very differently:

js
// Loose equality (==) - converts types before comparing
0 == "0"          // true
0 == ""           // true
null == undefined // true
1 == true         // true

// Strict equality (===) - types must match
0 === "0"         // false
null === undefined // false
1 === true        // false
The one rule
Always use === and !==. The only exception worth knowing: x == null is a clean way to check for both null and undefined at once.

The famous coercion gotchas

JavaScript's type coercion has a few rules that produce results nobody would design today. They're in the language forever, so let's look at them head-on:

js
// + with a string: everything becomes a string
1 + "1"           // "11"
"5" + 3           // "53"
[] + []           // ""   (both arrays become "")
[] + {}           // "[object Object]"
{} + []           // 0    (parsed as a block + array - yes, really)

// Math operators that aren't + coerce to number
"5" - 3           // 2
"5" * "2"         // 10
"abc" - 1         // NaN

// NaN is never equal to itself
NaN === NaN       // false
Number.isNaN(NaN) // true   (use this)

// Truthy/falsy values
Boolean(0)        // false
Boolean("")       // false
Boolean(null)     // false
Boolean(undefined)// false
Boolean(NaN)      // false
Boolean("0")      // true   ⚠ a string "0" is truthy
Boolean([])       // true   ⚠ empty array is truthy
Boolean({})       // true   ⚠ empty object is truthy
The six falsy values
Only these are falsy: false, 0, "", null, undefined, NaN. Everything else is truthy, including "0", [], and {}.

Logical operators

&& and ||don't return booleans. They return one of their operands. This is how short-circuiting works:

js
// || returns the first truthy value (or the last if all falsy)
"" || "default"            // "default"
0 || 42                    // 42
"hi" || "fallback"         // "hi"

// && returns the first falsy value (or the last if all truthy)
"hi" && 42                 // 42
0 && doSomething()         // 0   - doSomething never runs

// Negation
!true                      // false
!!"hi"                     // true (the "to-boolean" trick)

Nullish coalescing: ??

|| is too aggressive when you only want to fall back on null or undefined. The ?? operator was added for exactly that:

js
const count = 0;

const a = count || 10;   // 10  - wrong! 0 is a valid value
const b = count ?? 10;   // 0   - only falls back if null/undefined

const name = "" ?? "anon";   // ""
const real = "" || "anon";   // "anon"

Reach for ?? when zero, empty string, or false are valid values you want to keep.

Optional chaining: ?.

Reading a property on undefined throws a TypeError. The ?. operator short-circuits to undefined instead:

js
const user = { name: "Ada" };

user.address.street          // TypeError
user.address?.street         // undefined  - safe

// Works on function calls and array access too
user.greet?.()               // undefined if greet is missing
arr?.[0]                     // safe array access

// Pair it with ?? for a default
const street = user.address?.street ?? "unknown";

Template literals

Backticks create strings that can span lines and embed any expression with ${...}:

js
const name = "Ada";
const age = 36;

const intro = `Hello, ${name}! You are ${age * 12} months old.`;
// "Hello, Ada! You are 432 months old."

const multiline = `line one
line two
line three`;

Try the gotchas live

Open the console and play. Try changing values, mixing types, breaking things on purpose. The fastest way to learn coercion is to watch it happen.

// The classics
console.log("[] + [] =", [] + []);              // ""
console.log("[] + {} =", [] + {});              // "[object Object]"
console.log("'5' + 3 =", "5" + 3);              // "53"
console.log("'5' - 3 =", "5" - 3);              // 2

// NaN
console.log("NaN === NaN:", NaN === NaN);       // false
console.log("isNaN:", Number.isNaN(NaN));        // true

// Truthy/falsy
console.log("'0' is truthy:", Boolean("0"));    // true (!)
console.log("[] is truthy:", Boolean([]));      // true (!)

// || vs ??
const count = 0;
console.log("|| with 0:", count || 99);         // 99
console.log("?? with 0:", count ?? 99);         // 0

// Optional chaining
const user = { name: "Grace" };
console.log("missing prop:", user.address?.street); // undefined

// Try your own
// console.log(true + true + true);   // ?
// console.log("b" + "a" + +"a" + "a"); // famous one

Why does JS do this?
Brendan Eich wrote JavaScript in 10 days in 1995. Coercion was meant to make beginner code "just work" in browsers that couldn't crash. The web kept that behavior forever for backward compatibility. Today we just use === and move on.

Quiz

Quiz1 / 4

What does [] + {} evaluate to?

Recap

  • Use === by default. == coerces and lies.
  • Six falsy values: false, 0, "", null, undefined, NaN. Everything else is truthy.
  • ?? falls back only on nullish. || falls back on any falsy.
  • ?. safely reads through possibly-undefined chains.
  • Template literals beat string concatenation for readability.
  • NaN === NaN is false. Use Number.isNaN.