Types
JavaScript is dynamically typed — variables have no declared type; types attach to values and may change through reassignment. The seven primitive types: undefined, null, boolean, number, string, symbol, bigint. The eighth type — object — covers everything else (objects, arrays, functions, dates, regexes, etc.). The typeof operator admits runtime inspection. Implicit type coercion is substantial — "5" + 1 is "51"; "5" - 1 is 4; the conventional defence is the strict equality operator (===) and explicit conversion functions. The combination — seven primitives plus object, dynamic typing with substantial coercion, the substantial Number caveats (NaN, Infinity, IEEE 754 quirks), the BigInt for substantial integers — is the substance of JavaScript’s type system.
The seven primitives
// undefined — absence of value
let x; // x is undefined
typeof undefined; // "undefined"
// null — explicit absence
const empty = null;
typeof null; // "object" (historical bug)
// boolean
const flag = true;
typeof flag; // "boolean"
// number — IEEE 754 double-precision float
const n = 42;
const f = 3.14;
const inf = Infinity;
const nan = NaN;
typeof n; // "number"
// string — immutable UTF-16
const s = "hello";
typeof s; // "string"
// symbol — unique, opaque identifier
const sym = Symbol("description");
typeof sym; // "symbol"
// bigint — arbitrary-precision integer (ES2020+)
const big = 100n;
const huge = 9007199254740993n;
typeof big; // "bigint"
object
The eighth type — object — covers everything composite:
const obj = { name: "Alice", age: 30 };
typeof obj; // "object"
const arr = [1, 2, 3];
typeof arr; // "object"
Array.isArray(arr); // true (use this for arrays)
const date = new Date();
typeof date; // "object"
const regex = /pattern/;
typeof regex; // "object"
const fn = () => {};
typeof fn; // "function" (a callable object)
Number
JavaScript numbers are IEEE 754 double-precision — admit substantial values but with floating-point quirks:
0.1 + 0.2; // 0.30000000000000004 (the conventional pitfall)
0.1 + 0.2 === 0.3; // false
Number.MAX_SAFE_INTEGER; // 9007199254740991 (2^53 - 1)
Number.MIN_SAFE_INTEGER; // -9007199254740991
Number.MAX_VALUE; // ~1.8 × 10^308
Number.MIN_VALUE; // ~5 × 10^-324
Number.EPSILON; // smallest representable difference
Number.POSITIVE_INFINITY; // === Infinity
Number.NaN; // === NaN
Number.isFinite(value); // strict (not NaN, not Infinity)
Number.isInteger(value);
Number.isSafeInteger(value); // within MAX/MIN_SAFE
Number.isNaN(value); // strict (only true NaN)
// Global isFinite/isNaN coerce — avoid:
isNaN("not a number"); // true (coerces "not a number" to NaN)
Number.isNaN("not a number"); // false (no coercion)
The conventional contemporary discipline:
- Use
Number.isNaNandNumber.isFiniteover the globals. - For exact decimal arithmetic, use libraries (
big.js,decimal.js). - For arbitrary-precision integers, use
BigInt.
Numeric literals
42 // decimal
0xFF // hex
0o755 // octal
0b1010 // binary
1e3 // scientific (1000)
1_000_000 // separator
3.14e-2 // scientific float
// BigInt:
100n
0xFFn
1_000_000n
NaN
NaN is the only value that’s not equal to itself:
NaN === NaN; // false
NaN !== NaN; // true
// Detection:
Number.isNaN(NaN); // true
Object.is(NaN, NaN); // true (the only difference from ===)
BigInt
For arbitrary-precision integers (ES2020+):
const big = 9007199254740993n;
const computed = 2n ** 100n; // 1267650600228229401496703205376n
// Cannot mix with Number:
big + 1n; // OK
big + 1; // TypeError
// Conversion:
BigInt(42); // 42n
Number(42n); // 42 (may lose precision)
String
Treated in Strings.
const s = "hello";
typeof s; // "string"
s.length; // 5
s[0]; // "h"
s + " world"; // "hello world"
`template ${literal}`; // template literal
'single quotes'; // also admitted
"double quotes";
Strings are immutable — methods return new strings.
Symbol
Symbols admit unique identifiers:
const a = Symbol("description");
const b = Symbol("description");
a === b; // false (always unique)
// As object keys:
const obj = {
[Symbol("private")]: "hidden"
};
// Well-known symbols:
Symbol.iterator; // for [Symbol.iterator]() {}
Symbol.asyncIterator;
Symbol.toPrimitive;
// Global registry:
const x = Symbol.for("shared");
const y = Symbol.for("shared");
x === y; // true
The mechanism admits substantial protocol-style polymorphism.
null vs undefined
The two principal “absent” values:
undefined | null | |
|---|---|---|
| typeof | "undefined" | "object" (bug) |
| Default | Yes | No |
| Conventional meaning | Not assigned / not provided | Explicitly empty |
let x; // undefined
const fn = () => {};
fn(); // returns undefined
const obj = {};
obj.foo; // undefined (missing property)
const empty = null; // explicit "no value"
The conventional discipline:
undefined— implicit absence (default values, missing properties).null— explicit absence (deliberate, return values).- Either — both are falsy.
For checking either:
if (x == null) { ... } // true for both null and undefined
if (x === null) { ... } // strict null
if (x === undefined) { ... } // strict undefined
The == null is one of the few admitted uses of == — admit substantial conciseness.
Type checking
typeof
typeof undefined; // "undefined"
typeof null; // "object" (historical bug)
typeof true; // "boolean"
typeof 42; // "number"
typeof "hello"; // "string"
typeof Symbol(); // "symbol"
typeof 100n; // "bigint"
typeof {}; // "object"
typeof []; // "object"
typeof () => {}; // "function"
typeof undeclared; // "undefined" (no error)
instanceof
For class/constructor checks:
[1, 2, 3] instanceof Array; // true
new Date() instanceof Date; // true
"hello" instanceof String; // false (primitive)
new String("hello") instanceof String; // true (object wrapper)
The instanceof checks the prototype chain — substantial for class hierarchies.
Specific checks
Array.isArray([1, 2, 3]); // true (preferred over instanceof)
Number.isInteger(42);
Number.isFinite(value);
Number.isNaN(value);
Object.prototype.toString.call(value); // "[object Type]" — substantial detection
// e.g., "[object Date]", "[object RegExp]", "[object Array]"
Type coercion
JavaScript admits substantial implicit conversion. The conventional pitfalls:
// String concatenation vs addition:
"5" + 1; // "51" (concat)
"5" - 1; // 4 (numeric)
"5" * "2"; // 10
+"5"; // 5 (unary plus)
// Equality:
1 == "1"; // true (coerced)
1 === "1"; // false (strict; preferred)
0 == false; // true (coerced)
null == undefined; // true
[] == false; // true (coerced)
[] == ![]; // true (substantial weirdness)
// Boolean context:
if ("0") { ... } // truthy ("0" is non-empty string)
if (0) { ... } // falsy
if ([]) { ... } // truthy (any object)
The conventional defence: use === and !==; explicit conversion functions:
Number("42"); // 42
Number("not a number"); // NaN
Number(true); // 1
Number(null); // 0
Number(undefined); // NaN
Number(""); // 0
String(42); // "42"
String(null); // "null"
String(undefined); // "undefined"
Boolean(0); // false
Boolean(""); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(NaN); // false
Boolean("anything else"); // true
Boolean({}); // true (any object)
parseInt("42px"); // 42 (parses prefix)
parseInt("0x1F", 16); // 31
parseFloat("3.14abc"); // 3.14
Truthy / falsy
The falsy values:
false0,-0,0n""(empty string)nullundefinedNaN
Everything else is truthy — including "0", "false", [], {}.
if (value) { ... } // truthy check
if (!value) { ... } // falsy check
const result = value ?? defaultValue; // null/undefined coalescing
const result = value || defaultValue; // any falsy → default
The conventional contemporary form for “default if null/undefined” is ?? (nullish coalescing) — admits 0 and "" as valid values:
const a = 0 ?? 10; // 0
const b = 0 || 10; // 10 (0 is falsy)
const c = "" ?? "default"; // ""
const d = "" || "default"; // "default"
Object types
Plain objects
const obj = { name: "Alice", age: 30 };
// Keys:
Object.keys(obj); // ["name", "age"]
Object.values(obj); // ["Alice", 30]
Object.entries(obj); // [["name", "Alice"], ["age", 30]]
// From entries:
Object.fromEntries([["a", 1], ["b", 2]]); // { a: 1, b: 2 }
// Inspection:
"name" in obj; // true
obj.hasOwnProperty("name"); // true
Object.hasOwn(obj, "name"); // true (modern, preferred)
// Modification:
obj.email = "alice@b.c"; // add
delete obj.age; // remove
// Spread / merge:
const merged = { ...obj, role: "admin" };
// Freeze:
Object.freeze(obj); // shallow immutable
Arrays
Treated in Data structures.
const arr = [1, 2, 3];
arr.length; // 3
arr[0]; // 1
// Methods:
arr.push(4); // mutate; add to end
arr.pop(); // mutate; remove from end
arr.shift(); // mutate; remove from start
arr.unshift(0); // mutate; add to start
arr.map(x => x * 2);
arr.filter(x => x > 1);
arr.reduce((a, b) => a + b, 0);
Date
const now = new Date();
const specific = new Date("2026-01-15T10:00:00Z");
const fromMs = new Date(1736937600000);
now.getTime(); // ms since epoch
now.getFullYear();
now.getMonth(); // 0-11
now.getDate(); // 1-31
now.toISOString(); // "2026-01-15T10:00:00.000Z"
Date.now(); // current ms
The native Date admits substantial pitfalls (mutable, 0-indexed months, timezone confusion). The conventional contemporary alternatives: date-fns, dayjs, the Temporal API (Stage 3 as of 2026).
Map and Set
const map = new Map();
map.set("key", "value");
map.set(1, "one"); // any key type
map.get("key");
map.has("key");
map.size;
map.delete("key");
for (const [k, v] of map) { ... }
const set = new Set([1, 2, 3, 2, 1]); // {1, 2, 3} (deduplicated)
set.add(4);
set.has(2);
set.size;
For object-keyed weak references:
const cache = new WeakMap(); // keys must be objects
const seen = new WeakSet();
A note on the conventional discipline
The contemporary JavaScript types advice:
- Use
===and!==— never==(except== null). - Use
??over||for default values. - Use
Number.isNaNandNumber.isFiniteover globals. - Use
Array.isArrayoverinstanceof Array. - Use
Object.hasOwnoverhasOwnProperty. - Use explicit conversions —
Number(),String(),Boolean(). - Use
BigIntfor large integers (over MAX_SAFE_INTEGER). - Use
nullfor explicit absence;undefinedfor default. - Use
Mapfor non-string keys; objects for string keys. - Use
Setfor unique-value collections. - Reach for TypeScript (treated separately) for substantial type safety.
The combination — seven primitives plus object, dynamic typing with substantial implicit coercion (and the conventional defences via ===, ??, explicit conversions), the IEEE 754 number type with substantial caveats, the BigInt for large integers, the substantial standard object types — is the substance of JavaScript’s type system. The discipline produces flexible code with substantial care required to avoid the conventional coercion pitfalls.