Part 5 — Data Types
Primitives, Reference types, typeof, instanceof, Object.prototype.toString, Boxing and wrapper objects
JavaScript has 8 types: 7 primitives + object.
graph TD
T[JavaScript Types] --> P[Primitives<br/>immutable, copied by value]
T --> O[Object<br/>mutable, copied by reference]
P --> S1[string]
P --> S2[number]
P --> S3[bigint]
P --> S4[boolean]
P --> S5[undefined]
P --> S6[null]
P --> S7[symbol]
O --> O1[plain object]
O --> O2[Array]
O --> O3[Function]
O --> O4[Date, Map, Set, RegExp, ...]
Primitives#
| Type | Example | Notes |
|---|---|---|
string |
"hi", 'hi', `hi` |
UTF-16 sequences; immutable |
number |
42, 3.14, Infinity, NaN |
IEEE-754 double (Part 13) |
bigint |
9007199254740993n |
Arbitrary-precision integers |
boolean |
true, false |
|
undefined |
undefined |
"declared but no value" |
null |
null |
"intentional absence" |
symbol |
Symbol("id") |
Unique, used as hidden keys |
Primitives are immutable. "abc".toUpperCase() returns a new string; it doesn't change the original.
⚠️ null vs undefined:
undefined— the engine's "not set yet" (uninitialized vars, missing params, missing props).null— your "deliberately empty."typeof undefined === "undefined", buttypeof null === "object"— a 25-year-old bug kept for compatibility. 🎯
Reference types#
Objects, arrays, functions, Date, Map, Set, WeakMap, WeakSet, RegExp, etc. All are objects underneath, all copied by reference.
Map— key→value store where any value can be a key (objects too), preserves insertion order, has.size. Prefer over plain objects for dynamic dictionaries.Set— unique values. Great for dedupe:[...new Set(arr)].WeakMap/WeakSet— keys must be objects and are weakly held: if nothing else references the key, it can be garbage-collected (and its entry vanishes). Not iterable, no.size. Use for private data / caches keyed by object without causing leaks.
const cache = new WeakMap();
function compute(objKey) {
if (cache.has(objKey)) return cache.get(objKey);
const result = /* expensive */ objKey.id * 2;
cache.set(objKey, result); // entry disappears when objKey is GC'd
return result;
}
typeof, instanceof, Object.prototype.toString#
typeof "hi" // "string"
typeof 42 // "number"
typeof 10n // "bigint"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object" ⚠️ the famous bug
typeof Symbol() // "symbol"
typeof function(){}// "function" ⚠️ functions get their own answer
typeof [] // "object" (arrays are objects)
typeof {} // "object"
typeof is unreliable for distinguishing object kinds. Use:
Array.isArray([]) // true — the correct array check
[] instanceof Array // true, but fails across iframes/realms
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(/x/) // "[object RegExp]"
instanceof checks whether a constructor's .prototype appears in an object's prototype chain — so it can be fooled and breaks across realms. Object.prototype.toString.call(x) is the most robust type tag. 🎯
Boxing and wrapper objects#
ELI12. "hi" is a primitive with no methods, yet "hi".toUpperCase() works. Behind the scenes JavaScript temporarily wraps the primitive in a String object, calls the method, then throws the wrapper away. This is autoboxing.
const s = "hi";
s.custom = 1; // silently wraps, sets prop on a throwaway object
console.log(s.custom); // undefined — the wrapper was discarded
typeof new String("hi") // "object" ⚠️ — never use `new String/Number/Boolean`
new Boolean(false) ? "truthy!" : "falsy" // "truthy!" — objects are always truthy 🎯
🔴 Never use wrapper constructors (new String, new Number, new Boolean). Call them without new for conversion (Number("42")) instead.
Interview questions (Part 5):
- Why is
typeof null === "object"? Name two robust alternatives for type checking. - Difference between
nullandundefined, and what each idiomatically signals. - When would you choose
Mapover a plain object?WeakMapoverMap? - Explain autoboxing. Why is
new Boolean(false)truthy?