JavaScript — Data Types & Coercion
JavaScript has a dynamic type system — variables can hold values of any type, and the type can change at runtime. Despite this flexibility, every value has a well-defined type at any given moment. Understanding these types and how JavaScript coerces values between them is essential for writing predictable code.
JavaScript distinguishes between primitive types (immutable values stored directly) and reference types (objects stored by reference). Primitives include string, number, boolean, null, undefined, symbol, and bigint. Everything else — objects, arrays, functions, dates, maps, sets — is a reference type.
Type coercion — the automatic or intentional conversion of values from one type to another — is one of JavaScript's most misunderstood features. Knowing the rules of coercion helps you avoid bugs and write cleaner code.
| 1 | // JavaScript's dynamic type system |
| 2 | let value = "hello"; // string |
| 3 | value = 42; // now a number |
| 4 | value = true; // now a boolean |
| 5 | value = { key: "val" }; // now an object |
| 6 | value = null; // now null |
| 7 | |
| 8 | // typeof reveals the type at runtime |
| 9 | console.log(typeof "hello"); // "string" |
| 10 | console.log(typeof 42); // "number" |
| 11 | console.log(typeof true); // "boolean" |
| 12 | console.log(typeof {}); // "object" |
| 13 | console.log(typeof undefined); // "undefined" |
Primitives are immutable values — once created, they cannot be changed. They are stored directly in the variable's memory location (on the stack). When you assign or compare primitives, you work with the actual value.
| Type | typeof Result | Examples | Notes |
|---|---|---|---|
| string | "string" | "hello", 'world', `template` | UTF-16 encoded, immutable sequence of characters |
| number | "number" | 42, 3.14, NaN, Infinity | 64-bit IEEE 754 floating point (no integer type) |
| boolean | "boolean" | true, false | Logical primitives for conditionals |
| undefined | "undefined" | undefined | Default value of uninitialized variables and missing return |
| null | "object" | null | Intentional absence — typeof null is a historic bug |
| symbol | "symbol" | Symbol("id") | Unique, immutable identifier (ES6) — guaranteed uniqueness |
| bigint | "bigint" | 9007199254740991n, BigInt(42) | Arbitrary precision integer (ES2020) |
String
| 1 | // Three quoting styles |
| 2 | const single = 'Single quotes'; |
| 3 | const double = "Double quotes"; |
| 4 | const template = `Template literals — support interpolation and multiline`; |
| 5 | |
| 6 | const name = "Alice"; |
| 7 | const greeting = `Hello, ${name}!`; // Template interpolation |
| 8 | console.log(greeting); // "Hello, Alice!" |
| 9 | |
| 10 | // Strings are immutable — methods return new strings |
| 11 | const str = " JavaScript "; |
| 12 | console.log(str.length); // 14 (including spaces) |
| 13 | console.log(str.trim()); // "JavaScript" |
| 14 | console.log(str.toUpperCase()); // " JAVASCRIPT " |
| 15 | console.log(str.toLowerCase()); // " javascript " |
| 16 | console.log(str.includes("Java")); // true |
| 17 | console.log(str.slice(2, 12)); // "JavaScript" |
| 18 | console.log("a".repeat(5)); // "aaaaa" |
| 19 | console.log("hello".charAt(1)); // "e" |
| 20 | |
| 21 | // Template literal features |
| 22 | const multiline = ` |
| 23 | This is |
| 24 | a multiline |
| 25 | string |
| 26 | `; |
| 27 | console.log(`2 + 2 = ${2 + 2}`); // "2 + 2 = 4" |
| 28 | |
| 29 | // Tagged templates |
| 30 | function highlight(strings, ...values) { |
| 31 | return strings.reduce((acc, str, i) => |
| 32 | acc + str + (values[i] ? `<mark>${values[i]}</mark>` : ""), ""); |
| 33 | } |
| 34 | const tagged = highlight`User ${name} is ${30} years old`; |
| 35 | console.log(tagged); |
| 36 | // "User <mark>Alice</mark> is <mark>30</mark> years old" |
Number
| 1 | // JavaScript has ONE number type — 64-bit floating point (IEEE 754) |
| 2 | const integer = 42; // Actually a float behind the scenes |
| 3 | const float = 3.14; |
| 4 | const scientific = 1.5e6; // 1500000 |
| 5 | const hex = 0xFF; // 255 |
| 6 | const octal = 0o77; // 63 (ES6) |
| 7 | const binary = 0b1010; // 10 (ES6) |
| 8 | const big = 1_000_000; // Numeric separators (ES2021) — readability |
| 9 | |
| 10 | // Special numeric values |
| 11 | console.log(1 / 0); // Infinity |
| 12 | console.log(-1 / 0); // -Infinity |
| 13 | console.log(0 / 0); // NaN (Not a Number) |
| 14 | console.log(Math.sqrt(-1)); // NaN |
| 15 | console.log(Number.MAX_VALUE); // 1.7976931348623157e+308 |
| 16 | console.log(Number.MIN_VALUE); // 5e-324 |
| 17 | console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2^53 - 1) |
| 18 | console.log(Number.EPSILON); // 2.220446049250313e-16 |
| 19 | |
| 20 | // Floating point precision issues |
| 21 | console.log(0.1 + 0.2); // 0.30000000000000004 (not 0.3!) |
| 22 | console.log(0.1 + 0.2 === 0.3); // false |
| 23 | |
| 24 | // Safe comparison for floats |
| 25 | function approxEqual(a, b, epsilon = Number.EPSILON) { |
| 26 | return Math.abs(a - b) < epsilon; |
| 27 | } |
| 28 | console.log(approxEqual(0.1 + 0.2, 0.3)); // true |
| 29 | |
| 30 | // Number methods |
| 31 | console.log(Number.isInteger(42)); // true |
| 32 | console.log(Number.isFinite(Infinity)); // false |
| 33 | console.log(Number.isNaN("NaN")); // false (no coercion) |
| 34 | console.log(isNaN("NaN")); // true (coerces — avoid) |
| 35 | console.log(parseInt("42px")); // 42 |
| 36 | console.log(parseFloat("3.14em")); // 3.14 |
| 37 | console.log((3.14159).toFixed(2)); // "3.14" |
BigInt
| 1 | // BigInt — arbitrary precision for large integers |
| 2 | const bigint = 9007199254740991n; |
| 3 | const fromNumber = BigInt(42); |
| 4 | const fromString = BigInt("9007199254740991"); |
| 5 | |
| 6 | // Operations with BigInt |
| 7 | console.log(bigint + 1n); // 9007199254740992n |
| 8 | console.log(bigint * 2n); // 18014398509481982n |
| 9 | |
| 10 | // Cannot mix BigInt and regular Number |
| 11 | console.log(bigint + 1); // TypeError: Cannot mix BigInt and other types |
| 12 | |
| 13 | // Comparisons work across types |
| 14 | console.log(1n === 1); // false (different types) |
| 15 | console.log(1n == 1); // true (loose equality coerces) |
| 16 | |
| 17 | // Only integers — no decimals |
| 18 | // console.log(1.5n); // SyntaxError |
| 19 | |
| 20 | // Division truncates toward zero |
| 21 | console.log(5n / 2n); // 2n |
Symbol
| 1 | // Symbol — unique, immutable identifiers |
| 2 | const sym1 = Symbol("description"); |
| 3 | const sym2 = Symbol("description"); |
| 4 | console.log(sym1 === sym2); // false — every Symbol is unique |
| 5 | |
| 6 | // Symbols as object keys (for "private" or metadata properties) |
| 7 | const id = Symbol("id"); |
| 8 | const user = { name: "Alice", [id]: 12345 }; |
| 9 | console.log(user[id]); // 12345 |
| 10 | console.log(user.id); // undefined — not a string key |
| 11 | |
| 12 | // Symbols are not enumerable in for...in or Object.keys |
| 13 | console.log(Object.keys(user)); // ["name"] |
| 14 | console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)] |
| 15 | |
| 16 | // Well-known symbols — customize behavior |
| 17 | const arr = [1, 2, 3]; |
| 18 | console.log(arr[Symbol.iterator]); // [Function: values] |
| 19 | |
| 20 | // Symbol.for — shared symbols across realms |
| 21 | const globalSym = Symbol.for("app.role"); |
| 22 | const sameSym = Symbol.for("app.role"); |
| 23 | console.log(globalSym === sameSym); // true — same symbol |
info
Reference types store a reference (memory address) to the actual data, not the data itself. When you assign or compare reference types, you are working with references. This distinction is critical for understanding mutation, equality, and memory behavior.
Object
| 1 | // Object literal — most common reference type |
| 2 | const person = { |
| 3 | name: "Alice", |
| 4 | age: 30, |
| 5 | greet() { |
| 6 | console.log(`Hi, I'm ${this.name}`); |
| 7 | }, |
| 8 | }; |
| 9 | |
| 10 | // Accessing properties |
| 11 | console.log(person.name); // "Alice" — dot notation |
| 12 | console.log(person["age"]); // 30 — bracket notation (for dynamic keys) |
| 13 | |
| 14 | // Adding and modifying properties |
| 15 | person.city = "New York"; |
| 16 | person.age = 31; |
| 17 | |
| 18 | // Deleting properties |
| 19 | delete person.city; |
| 20 | |
| 21 | // Property existence |
| 22 | console.log("name" in person); // true |
| 23 | console.log(person.hasOwnProperty("name")); // true |
| 24 | |
| 25 | // Object.keys, values, entries |
| 26 | console.log(Object.keys(person)); // ["name", "age"] |
| 27 | console.log(Object.values(person)); // ["Alice", 31] |
| 28 | console.log(Object.entries(person)); // [["name","Alice"],["age",31]] |
| 29 | |
| 30 | // Merging objects |
| 31 | const defaults = { theme: "dark", lang: "en" }; |
| 32 | const userPrefs = { theme: "light" }; |
| 33 | const config = { ...defaults, ...userPrefs }; |
| 34 | console.log(config); // { theme: "light", lang: "en" } |
Array
| 1 | // Arrays — ordered, zero-indexed collections |
| 2 | const fruits = ["apple", "banana", "cherry"]; |
| 3 | console.log(fruits[0]); // "apple" |
| 4 | console.log(fruits.length); // 3 |
| 5 | |
| 6 | // Adding and removing |
| 7 | fruits.push("date"); // add to end — ["apple","banana","cherry","date"] |
| 8 | fruits.pop(); // remove from end — returns "date" |
| 9 | fruits.unshift("apricot"); // add to start |
| 10 | fruits.shift(); // remove from start |
| 11 | |
| 12 | // Finding elements |
| 13 | console.log(fruits.indexOf("banana")); // 1 |
| 14 | console.log(fruits.includes("apple")); // true |
| 15 | console.log(fruits.find(f => f.startsWith("b"))); // "banana" |
| 16 | console.log(fruits.findIndex(f => f.startsWith("c"))); // 2 |
| 17 | |
| 18 | // Slicing and splicing |
| 19 | console.log(fruits.slice(1, 3)); // ["banana", "cherry"] — non-destructive |
| 20 | console.log(fruits); // original unchanged |
| 21 | |
| 22 | // Multi-dimensional arrays |
| 23 | const matrix = [ |
| 24 | [1, 2, 3], |
| 25 | [4, 5, 6], |
| 26 | [7, 8, 9], |
| 27 | ]; |
| 28 | console.log(matrix[1][2]); // 6 |
| 29 | |
| 30 | // Array-like checks |
| 31 | console.log(Array.isArray(fruits)); // true |
| 32 | console.log(Array.isArray({})); // false |
Map & Set
| 1 | // Map — key-value with ANY key type (not just strings) |
| 2 | const map = new Map(); |
| 3 | const objKey = { id: 1 }; |
| 4 | const fnKey = () => {}; |
| 5 | |
| 6 | map.set("string", "value"); |
| 7 | map.set(42, "number key"); |
| 8 | map.set(objKey, "object key"); |
| 9 | map.set(fnKey, "function key"); |
| 10 | |
| 11 | console.log(map.get(42)); // "number key" |
| 12 | console.log(map.get(objKey)); // "object key" |
| 13 | console.log(map.has("string")); // true |
| 14 | console.log(map.size); // 4 |
| 15 | map.delete(42); |
| 16 | console.log(map.size); // 3 |
| 17 | |
| 18 | // Map preserves insertion order |
| 19 | for (const [key, value] of map) { |
| 20 | console.log(key, value); |
| 21 | } |
| 22 | |
| 23 | // Set — unique values (no duplicates) |
| 24 | const set = new Set([1, 2, 3, 3, 3, 4, 5]); |
| 25 | console.log(set); // Set { 1, 2, 3, 4, 5 } |
| 26 | console.log(set.size); // 5 |
| 27 | |
| 28 | set.add(6); |
| 29 | set.add(1); // ignored — already exists |
| 30 | console.log(set.has(3)); // true |
| 31 | set.delete(3); |
| 32 | |
| 33 | // Set iteration |
| 34 | for (const value of set) { |
| 35 | console.log(value); |
| 36 | } |
| 37 | |
| 38 | // Practical: deduplication |
| 39 | const duplicates = [1, 2, 2, 3, 3, 3, 4]; |
| 40 | const unique = [...new Set(duplicates)]; |
| 41 | console.log(unique); // [1, 2, 3, 4] |
| 42 | |
| 43 | // Practical: set operations |
| 44 | const a = new Set([1, 2, 3]); |
| 45 | const b = new Set([3, 4, 5]); |
| 46 | const union = new Set([...a, ...b]); // {1,2,3,4,5} |
| 47 | const intersection = new Set([...a].filter(x => b.has(x))); // {3} |
Date
| 1 | // Date — working with dates and times |
| 2 | const now = new Date(); |
| 3 | console.log(now.toString()); |
| 4 | console.log(now.toISOString()); |
| 5 | console.log(now.toLocaleDateString()); |
| 6 | |
| 7 | // Creating dates |
| 8 | const specific = new Date(2026, 6, 7, 14, 30, 0); |
| 9 | // Note: months are 0-indexed (0 = January) |
| 10 | const fromISO = new Date("2026-07-07T14:30:00Z"); |
| 11 | const fromMs = new Date(1780000000000); |
| 12 | |
| 13 | // Getting date components |
| 14 | const d = new Date(); |
| 15 | console.log(d.getFullYear()); // 2026 |
| 16 | console.log(d.getMonth()); // 6 (July is 6) |
| 17 | console.log(d.getDate()); // 7 |
| 18 | console.log(d.getDay()); // 2 (Tuesday — 0=Sun, 1=Mon, 2=Tue) |
| 19 | console.log(d.getHours()); // 14 |
| 20 | console.log(d.getTime()); // milliseconds since epoch |
| 21 | |
| 22 | // Date arithmetic |
| 23 | const tomorrow = new Date(now); |
| 24 | tomorrow.setDate(now.getDate() + 1); |
| 25 | console.log(diffInDays(d1, d2)); // helper needed |
| 26 | |
| 27 | // Timestamp comparison |
| 28 | console.log(now.getTime() > specific.getTime()); |
warning
Type coercion is JavaScript's automatic conversion of values from one type to another. It happens in three contexts: string coercion (when using + with a string), number coercion (for arithmetic, comparisons, and loose equality), and boolean coercion (in conditionals and logical operators).
String Coercion
| 1 | // String coercion happens with the + operator when one operand is a string |
| 2 | console.log("Hello" + 42); // "Hello42" |
| 3 | console.log("5" + 3); // "53" |
| 4 | console.log("5" + true); // "5true" |
| 5 | console.log("5" + null); // "5null" |
| 6 | console.log("5" + undefined); // "5undefined" |
| 7 | |
| 8 | // Explicit string conversion |
| 9 | console.log(String(42)); // "42" |
| 10 | console.log(String(true)); // "true" |
| 11 | console.log(String(null)); // "null" |
| 12 | console.log(String(undefined)); // "undefined" |
| 13 | console.log((42).toString()); // "42" |
| 14 | |
| 15 | // Template literals also coerce |
| 16 | console.log(`Value: ${42}`); // "Value: 42" |
| 17 | |
| 18 | // String coercion is NOT the same as JSON serialization |
| 19 | console.log(JSON.stringify(42)); // "42" |
| 20 | console.log(JSON.stringify(null)); // "null" |
| 21 | console.log(JSON.stringify(undefined)); // undefined (not "undefined"!) |
Number Coercion
| 1 | // Number coercion — arithmetic operators (except + with strings) |
| 2 | console.log("5" - 2); // 3 |
| 3 | console.log("5" * "2"); // 10 |
| 4 | console.log("10" / 2); // 5 |
| 5 | console.log("5" - true); // 4 (true → 1) |
| 6 | console.log("5" - false); // 5 (false → 0) |
| 7 | console.log("5" - null); // 5 (null → 0) |
| 8 | |
| 9 | // Unary plus — explicit number conversion |
| 10 | console.log(+"42"); // 42 |
| 11 | console.log(+true); // 1 |
| 12 | console.log(+false); // 0 |
| 13 | console.log(+null); // 0 |
| 14 | console.log(+undefined); // NaN |
| 15 | console.log(+"hello"); // NaN |
| 16 | |
| 17 | // Explicit conversion with Number() |
| 18 | console.log(Number("42")); // 42 |
| 19 | console.log(Number("3.14")); // 3.14 |
| 20 | console.log(Number("")); // 0 |
| 21 | console.log(Number(" ")); // 0 |
| 22 | console.log(Number("42px")); // NaN |
| 23 | console.log(Number(true)); // 1 |
| 24 | console.log(Number(null)); // 0 |
| 25 | console.log(Number(undefined)); // NaN |
| 26 | |
| 27 | // parseInt and parseFloat — more lenient than Number() |
| 28 | console.log(parseInt("42px")); // 42 |
| 29 | console.log(parseInt(" 42 ")); // 42 |
| 30 | console.log(parseInt("3.14")); // 3 (stops at decimal) |
| 31 | console.log(parseFloat("3.14em")); // 3.14 |
| 32 | console.log(parseInt("abc")); // NaN |
| 33 | |
| 34 | // parseInt with radix — always specify! |
| 35 | console.log(parseInt("0xFF", 16)); // 255 |
| 36 | console.log(parseInt("1010", 2)); // 10 |
| 37 | console.log(parseInt("077", 10)); // 77 (without radix: 63 in old engines) |
Boolean Coercion & Truthy/Falsy
| 1 | // Falsy values — coerce to false in boolean contexts |
| 2 | console.log(Boolean(false)); // false |
| 3 | console.log(Boolean(0)); // false |
| 4 | console.log(Boolean(-0)); // false |
| 5 | console.log(Boolean("")); // false |
| 6 | console.log(Boolean(null)); // false |
| 7 | console.log(Boolean(undefined)); // false |
| 8 | console.log(Boolean(NaN)); // false |
| 9 | |
| 10 | // Everything else is truthy |
| 11 | console.log(Boolean("false")); // true (non-empty string!) |
| 12 | console.log(Boolean("0")); // true (non-empty string!) |
| 13 | console.log(Boolean([])); // true (empty array is truthy) |
| 14 | console.log(Boolean({})); // true (empty object is truthy) |
| 15 | console.log(Boolean(42)); // true |
| 16 | console.log(Boolean(Infinity)); // true |
| 17 | |
| 18 | // Falsy check patterns |
| 19 | const name = ""; |
| 20 | if (name) { |
| 21 | console.log("This never runs — empty string is falsy"); |
| 22 | } |
| 23 | |
| 24 | // Truthy guard |
| 25 | const userInput = " "; |
| 26 | if (userInput.trim()) { |
| 27 | console.log("User entered non-whitespace"); |
| 28 | } |
| 29 | |
| 30 | // Double NOT — explicit boolean coercion |
| 31 | const value = 0; |
| 32 | console.log(!!value); // false |
| 33 | console.log(!!"hello"); // true |
| 34 | |
| 35 | // Boolean() — explicit conversion |
| 36 | console.log(Boolean(1)); // true |
| 37 | |
| 38 | // Logical operators return the actual value, not a boolean |
| 39 | console.log("hello" && 42); // 42 (last truthy) |
| 40 | console.log(null || "default"); // "default" (first truthy) |
| 41 | console.log(0 ?? "default"); // 0 (nullish coalescing — only null/undefined) |
best practice
typeof and instanceof are two operators for checking types. typeof works on primitives and returns a string. instanceof checks whether an object is an instance of a constructor in its prototype chain.
| Operator | Works On | Returns | Example |
|---|---|---|---|
| typeof | Any value | string (type name) | typeof 42 → "number" |
| instanceof | Objects only | boolean | arr instanceof Array → true |
| 1 | // typeof — returns type as a string |
| 2 | console.log(typeof 42); // "number" |
| 3 | console.log(typeof "hello"); // "string" |
| 4 | console.log(typeof true); // "boolean" |
| 5 | console.log(typeof undefined); // "undefined" |
| 6 | console.log(typeof Symbol()); // "symbol" |
| 7 | console.log(typeof 42n); // "bigint" |
| 8 | console.log(typeof null); // "object" (historic bug) |
| 9 | console.log(typeof {}); // "object" |
| 10 | console.log(typeof []); // "object" |
| 11 | console.log(typeof function(){}); // "function" |
| 12 | |
| 13 | // instanceof — checks prototype chain |
| 14 | console.log([] instanceof Array); // true |
| 15 | console.log({} instanceof Object); // true |
| 16 | console.log(new Date() instanceof Date); // true |
| 17 | console.log(/regex/ instanceof RegExp); // true |
| 18 | |
| 19 | // instanceof with custom constructors |
| 20 | class Animal {} |
| 21 | class Dog extends Animal {} |
| 22 | const dog = new Dog(); |
| 23 | console.log(dog instanceof Dog); // true |
| 24 | console.log(dog instanceof Animal); // true |
| 25 | console.log(dog instanceof Object); // true |
| 26 | |
| 27 | // instanceof pitfalls — across realms (iframes, windows) |
| 28 | const iframe = document.createElement("iframe"); |
| 29 | document.body.appendChild(iframe); |
| 30 | const IframeArray = iframe.contentWindow.Array; |
| 31 | const arr = new IframeArray(1, 2, 3); |
| 32 | console.log(arr instanceof Array); // false (different realm!) |
| 33 | console.log(Array.isArray(arr)); // true (works across realms) |
| 34 | |
| 35 | // typeof pitfalls — the null bug |
| 36 | const value = null; |
| 37 | console.log(typeof value === "object"); // true — use value === null instead |
| 38 | |
| 39 | // Checking for array |
| 40 | console.log(Array.isArray([])); // true — best practice |
| 41 | console.log(typeof [] === "object"); // true — not helpful |
pro tip
Understanding the difference between value types (primitives) and reference types (objects) is fundamental to JavaScript. Primitives are compared by value; objects are compared by reference. This distinction affects assignment, comparison, and function parameter behavior.
| 1 | // Primitives — compared by VALUE |
| 2 | const a = 42; |
| 3 | const b = 42; |
| 4 | console.log(a === b); // true — same value |
| 5 | |
| 6 | const s1 = "hello"; |
| 7 | const s2 = "hello"; |
| 8 | console.log(s1 === s2); // true — same value |
| 9 | |
| 10 | // Primitives are copied by value |
| 11 | let x = 10; |
| 12 | let y = x; // y gets a COPY of x's value (10) |
| 13 | x = 20; |
| 14 | console.log(y); // 10 — y is unaffected |
| 15 | |
| 16 | // Objects — compared by REFERENCE |
| 17 | const obj1 = { value: 42 }; |
| 18 | const obj2 = { value: 42 }; |
| 19 | console.log(obj1 === obj2); // false — different references |
| 20 | |
| 21 | const obj3 = obj1; // obj3 gets a COPY of the reference |
| 22 | console.log(obj1 === obj3); // true — same reference |
| 23 | |
| 24 | // Objects are passed by reference (copy of the reference) |
| 25 | function mutate(obj) { |
| 26 | obj.modified = true; |
| 27 | obj = { newObj: true }; // reassigning the parameter — does NOT affect original |
| 28 | } |
| 29 | const original = { value: 1 }; |
| 30 | mutate(original); |
| 31 | console.log(original.modified); // true |
| 32 | console.log(original.newObj); // undefined |
| 33 | |
| 34 | // Shallow vs deep copy |
| 35 | const nested = { a: 1, b: { c: 2 } }; |
| 36 | const shallow = { ...nested }; // shallow copy |
| 37 | shallow.a = 99; // OK — does not affect nested |
| 38 | shallow.b.c = 999; // Mutates nested! Shared reference |
| 39 | console.log(nested.b.c); // 999 |
| 40 | |
| 41 | const deep = JSON.parse(JSON.stringify(nested)); // deep copy |
| 42 | deep.b.c = 888; |
| 43 | console.log(nested.b.c); // 999 (unchanged after shallow mutation) |
danger
NaN
| 1 | // NaN — the only value not equal to itself |
| 2 | console.log(NaN === NaN); // false (I) |
| 3 | console.log(NaN !== NaN); // true (I know) |
| 4 | |
| 5 | // Checking for NaN |
| 6 | console.log(Number.isNaN(NaN)); // true — use this |
| 7 | console.log(Number.isNaN("hello")); // false — no coercion |
| 8 | console.log(isNaN("hello")); // true — coerces! Avoid |
| 9 | |
| 10 | // Operations that produce NaN |
| 11 | console.log(0 / 0); // NaN |
| 12 | console.log(Math.sqrt(-1)); // NaN |
| 13 | console.log(parseInt("abc")); // NaN |
| 14 | console.log(undefined + 1); // NaN |
| 15 | console.log("hello" - 1); // NaN |
| 16 | |
| 17 | // NaN in arrays — indexOf/findIndex won't find NaN |
| 18 | const arr = [1, NaN, 2]; |
| 19 | console.log(arr.indexOf(NaN)); // -1 (not found!) |
| 20 | console.log(arr.includes(NaN)); // true (ES2016 — uses SameValueZero) |
| 21 | |
| 22 | // Using Object.is for NaN comparison |
| 23 | console.log(Object.is(NaN, NaN)); // true |
| 24 | console.log(Object.is(0, -0)); // false |
Infinity & -0
| 1 | // Infinity and -Infinity |
| 2 | console.log(1 / 0); // Infinity |
| 3 | console.log(-1 / 0); // -Infinity |
| 4 | console.log(Number.MAX_VALUE * 2); // Infinity |
| 5 | |
| 6 | console.log(Infinity > 1e308); // true |
| 7 | console.log(-Infinity < -1e308); // true |
| 8 | console.log(Infinity + 1); // Infinity |
| 9 | console.log(Infinity - Infinity); // NaN |
| 10 | console.log(1 / Infinity); // 0 |
| 11 | |
| 12 | // isFinite — safe check for finite numbers |
| 13 | console.log(isFinite(42)); // true |
| 14 | console.log(isFinite(Infinity)); // false |
| 15 | console.log(isFinite(NaN)); // false |
| 16 | console.log(isFinite("42")); // true (coerces) |
| 17 | |
| 18 | // Negative zero (-0) |
| 19 | console.log(-0); // -0 |
| 20 | console.log(Object.is(0, -0)); // false |
| 21 | console.log(0 === -0); // true (=== treats them as equal) |
| 22 | |
| 23 | // When -0 appears |
| 24 | console.log(-0); // -0 |
| 25 | console.log(1 / -Infinity); // -0 |
| 26 | console.log(-1 * 0); // -0 |
| 27 | |
| 28 | // -0 serialization |
| 29 | console.log(String(-0)); // "0" |
| 30 | console.log(JSON.stringify(-0)); // "0" |
| 31 | console.log(Object.is(-0, 0)); // false |
warning
Essential Guidelines
Common Pitfalls
✗ typeof null === "object"
console.log(typeof null); // "object" — it's a bug from 1995Use value === null to check for null, never typeof value === "object".
✗ Floating Point Arithmetic
console.log(0.1 + 0.2 === 0.3); // falseNever compare floats directly. Use an epsilon tolerance or a decimal library for financial calculations.
✗ Loose Equality Surprises
console.log([] == false); // true. console.log([1] == 1); // trueThe == operator follows complex coercion rules. Always use === for predictable comparisons.
✗ NaN !== NaN
if (myValue === NaN) NaN is never equal to itself. Use Number.isNaN(myValue) or Object.is(myValue, NaN).
best practice