Polyglot
Languages Web (HTML / CSS / JS) types
Web (HTML / CSS / JS) § types

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.isNaN and Number.isFinite over 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:

undefinednull
typeof"undefined""object" (bug)
DefaultYesNo
Conventional meaningNot assigned / not providedExplicitly 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:

  • false
  • 0, -0, 0n
  • "" (empty string)
  • null
  • undefined
  • NaN

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.isNaN and Number.isFinite over globals.
  • Use Array.isArray over instanceof Array.
  • Use Object.hasOwn over hasOwnProperty.
  • Use explicit conversionsNumber(), String(), Boolean().
  • Use BigInt for large integers (over MAX_SAFE_INTEGER).
  • Use null for explicit absence; undefined for default.
  • Use Map for non-string keys; objects for string keys.
  • Use Set for 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.