Data Types, Shallow/Deep Copy & Mutability
The difference between primitives and reference types is the source of more React bugs, Redux anti-patterns, and debugging sessions than almost any other JavaScript concept. Senior engineers internalize the memory model.
Primitive vs Reference Types
Primitives (Immutable, Stored by Value)
// 7 primitive types
typeof 42; // 'number'
typeof 'hello'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof null; // 'object' (historical bug â it's actually a primitive)
typeof Symbol(); // 'symbol'
typeof 42n; // 'bigint'Primitives are copied by value:
let a = 10;
let b = a; // b gets a COPY of the value
b = 20;
console.log(a); // 10 â unaffectedPrimitives are immutable â you can't change "hello" in memory, only replace the variable's reference:
let str = 'hello';
str[0] = 'H';
console.log(str); // 'hello' â strings are immutable
str = 'Hello'; // New string created, variable reassignedReference Types (Mutable, Stored by Reference)
typeof {}; // 'object'
typeof []; // 'object' (arrays are objects)
typeof function(){}; // 'function' (functions are objects)
typeof new Date(); // 'object'
typeof new Map(); // 'object'References are copied by reference:
const a = { name: 'Alex' };
const b = a; // b points to the SAME object in memory
b.name = 'Sam';
console.log(a.name); // 'Sam' â a is affected!The Equality Trap
// Primitives: compared by value
'hello' === 'hello'; // true
42 === 42; // true
// References: compared by reference (memory address)
{} === {}; // false â different objects
[] === []; // false â different arrays
{ a: 1 } === { a: 1 }; // false
const obj = { a: 1 };
const ref = obj;
obj === ref; // true â same referenceThis is why React's === comparison for re-renders matters â returning { ...state } creates a new reference and triggers a re-render, even if the data is identical.
Shallow Copy
A shallow copy creates a new object but copies property values by reference:
const original = {
name: 'Alex',
address: { city: 'NYC', zip: '10001' },
tags: ['dev', 'senior'],
};
// Shallow copy methods
const copy1 = { ...original };
const copy2 = Object.assign({}, original);
const copy3 = Array.isArray(original) ? [...original] : { ...original };
// Top-level properties are independent
copy1.name = 'Sam';
console.log(original.name); // 'Alex' â unaffected
// Nested objects are STILL shared references
copy1.address.city = 'LA';
console.log(original.address.city); // 'LA' â AFFECTED!
copy1.tags.push('architect');
console.log(original.tags); // ['dev', 'senior', 'architect'] â AFFECTED!Array Shallow Copy Methods
const arr = [1, [2, 3], { a: 4 }];
const copy1 = [...arr];
const copy2 = arr.slice();
const copy3 = Array.from(arr);
const copy4 = arr.concat();
copy1[0] = 99; // Independent (primitive)
copy1[1].push(4); // Shared! (reference)
console.log(arr[1]); // [2, 3, 4]Deep Copy
A deep copy recursively copies all nested structures:
structuredClone() (Modern, Preferred)
const original = {
name: 'Alex',
address: { city: 'NYC' },
date: new Date(),
regex: /test/gi,
set: new Set([1, 2, 3]),
map: new Map([['key', 'value']]),
};
const deep = structuredClone(original);
deep.address.city = 'LA';
console.log(original.address.city); // 'NYC' â fully independentstructuredClone handles: Date, RegExp, Map, Set, ArrayBuffer, Error, nested objects/arrays, circular references.
structuredClone does NOT handle: Functions, DOM nodes, Symbols, property descriptors (getters/setters), prototype chain.
JSON Round-Trip (Legacy Approach)
const deep = JSON.parse(JSON.stringify(original));Loses: undefined, functions, Date (becomes string), RegExp, Map, Set, Infinity, NaN, circular references (throws).
Manual Deep Clone
function deepClone(obj, seen = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (seen.has(obj)) return seen.get(obj);
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
if (obj instanceof Map) {
const map = new Map();
seen.set(obj, map);
obj.forEach((v, k) => map.set(deepClone(k, seen), deepClone(v, seen)));
return map;
}
if (obj instanceof Set) {
const set = new Set();
seen.set(obj, set);
obj.forEach(v => set.add(deepClone(v, seen)));
return set;
}
const clone = Array.isArray(obj) ? [] : {};
seen.set(obj, clone);
for (const key of Reflect.ownKeys(obj)) {
clone[key] = deepClone(obj[key], seen);
}
return clone;
}The seen WeakMap handles circular references â without it, { a: obj } where obj.self = obj would cause infinite recursion.
Immutability in Practice
Immutable Update Patterns
// Object â spread to create new reference
const updated = { ...user, name: 'Sam' };
// Nested object â spread at each level
const updated = {
...state,
user: {
...state.user,
address: { ...state.user.address, city: 'LA' },
},
};
// Array â use non-mutating methods
const added = [...items, newItem];
const removed = items.filter(item => item.id !== targetId);
const updated = items.map(item =>
item.id === targetId ? { ...item, done: true } : item
);Object.freeze (Shallow)
const config = Object.freeze({
api: 'https://api.example.com',
nested: { timeout: 5000 },
});
config.api = 'changed'; // Silently fails (or TypeError in strict mode)
config.nested.timeout = 9999; // WORKS â freeze is shallowFor deep freeze:
function deepFreeze(obj) {
Object.freeze(obj);
Object.getOwnPropertyNames(obj).forEach(key => {
if (typeof obj[key] === 'object' && obj[key] !== null && !Object.isFrozen(obj[key])) {
deepFreeze(obj[key]);
}
});
return obj;
}Type Coercion Gotchas
// Equality weirdness (== uses coercion)
'' == 0; // true
0 == false; // true
null == undefined; // true
null == 0; // false (special case)
NaN == NaN; // false
// Always use === (strict equality)
'' === 0; // false
0 === false; // falseChecking Types Correctly
Array.isArray([]); // true (don't use typeof)
Number.isNaN(NaN); // true (don't use global isNaN)
Number.isFinite(42); // true
Object.prototype.toString.call(null); // "[object Null]"Interview Signal
Senior candidates demonstrate:
- Memory model â Primitives copied by value, objects by reference, equality implications
- Copy depth awareness â Spread/Object.assign are shallow,
structuredClonefor deep, JSON.parse limitations - Circular reference handling â WeakMap-based seen set in manual deep clone
- Immutable patterns â Spread-based updates for React/Redux state, why new references trigger re-renders
structuredCloneâ Modern API, what it handles vs what it doesn't, when to use alternatives