Object Type — Nuances and Pitfalls
Why null is object, arrays are object, and how not to break code when copying
Object — a backpack with labeled pockets
A primitive (number, string) is one simple thing: a coin, a key. An object is a backpack with named pockets. Pocket 'name' → 'John', pocket 'age' → 25. Inside can be other backpacks. That's the main feature — object stores STRUCTURED data with named fields.
What object really is
Object is a collection of key: value pairs. Keys are strings (or Symbol), values are any type.
const user = {
name: "John", // key: 'name', value: string
age: 25, // key: 'age', value: number
isStudent: true, // key: 'isStudent', value: boolean
address: { // key: 'address', value: another object!
city: "London",
zip: "SW1A",
},
};
// Accessing properties
console.log(user.name); // "John" — dot notation
console.log(user["name"]); // "John" — bracket notation
console.log(user.address.city); // "London" — nested accessGotcha #1: typeof null === "object"
This is one of JavaScript's most famous bugs:
console.log(typeof null); // "object" ← BUG IN JS ITSELF
console.log(typeof {}); // "object" — correct here
console.log(typeof []); // "object" — arrays are also object!typeof null === "object" — this is a bug from 1995 (first week of JavaScript's existence). Null is not an object, but it can't be fixed: too much old code relies on it.
How to properly check for null:
// WRONG:
if (typeof value === "object") { ... } // catches null too!
// CORRECT:
if (value !== null && typeof value === "object") { ... }Gotcha #2: Arrays are also object
let fruits = ["apple", "banana"];
console.log(typeof fruits); // "object" — not "array"!
console.log(Array.isArray(fruits)); // true — the correct check
console.log(Array.isArray({})); // false
// Why? Array in JS is an object with special numeric keys:
let arr = ["a", "b", "c"];
// Actually: { 0: "a", 1: "b", 2: "c", length: 3 }
console.log(arr[0]); // "a"
console.log(arr.length); // 3Rule: to check if something is an array — always use Array.isArray(value), not typeof.
Gotcha #3: Primitive vs Reference
This is THE MOST IMPORTANT object gotcha. Primitives store values, objects store references to a memory location.
// Primitives — copied by value
let a = 5;
let b = a; // b gets a COPY of value 5
b = 10;
console.log(a); // 5 — a did NOT change!
// Objects — copied by reference
let user1 = { name: "John" };
let user2 = user1; // user2 points to THE SAME object!
user2.name = "Maria";
console.log(user1.name); // "Maria" — user1 ALSO changed!user2 = user1 does not copy the object — only passes the address in memory. Both variables point to the same place.
{ ...original } — spread operator. It copies ALL properties into a new object. This is a 'shallow copy'.
Gotcha #4: Objects compared by reference
// Primitives compared by VALUE
console.log(5 === 5); // true — same values
console.log("JS" === "JS"); // true
// Objects compared by REFERENCE (memory address)
console.log({} === {}); // false — different objects!
console.log([] === []); // false — different arrays!
let a = { x: 1 };
let b = a; // b points to the SAME object
console.log(a === b); // true — same address!{} === {} — false, because these are two different objects in different memory locations, even if they look identical.
How to compare contents? Convert to string: JSON.stringify(obj1) === JSON.stringify(obj2)
Gotcha #5: Shallow copy vs Deep copy
// Shallow copy — spread
let user = { name: "John", address: { city: "London" } };
let copy = { ...user };
copy.name = "Maria"; // OK — primitive was copied
copy.address.city = "Paris"; // DANGEROUS!
console.log(user.name); // "John" — unchanged ✓
console.log(user.address.city); // "Paris" — CHANGED! ✗
// Spread only copies the first level!
// address — nested object, passed by reference.
// Deep copy — via JSON
let deepCopy = JSON.parse(JSON.stringify(user));
deepCopy.address.city = "Berlin";
console.log(user.address.city); // "Paris" — unchanged ✓Spread ({...obj}) — shallow copy. For nested objects — use JSON.parse(JSON.stringify(obj)) or structuredClone(obj) (modern method).
Two most common object mistakes: 1. let copy = obj — you think you copied, but both variables point to the same object. 2. typeof null === 'object' — checking the type and forgetting to exclude null. Memorize: for copying — spread {...obj}, for null check — value !== null.
structuredClone() — built-in function for deep copying. Supported in all modern browsers.