DNA⚡ JavaScriptData Types, Shallow/Deep Copy & Mutability
ðŸĢHatchlingJavaScriptData TypesImmutabilityFundamentals

Data Types, Shallow/Deep Copy & Mutability

Primitives are immutable. Objects are references. Misunderstanding this distinction causes some of the most insidious bugs in JavaScript — stale state, unintended mutations, and broken equality checks.

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 — unaffected

Primitives 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 reassigned

Reference 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 reference

This 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 independent

structuredClone 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 shallow

For 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;      // false

Checking 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:

  1. Memory model — Primitives copied by value, objects by reference, equality implications
  2. Copy depth awareness — Spread/Object.assign are shallow, structuredClone for deep, JSON.parse limitations
  3. Circular reference handling — WeakMap-based seen set in manual deep clone
  4. Immutable patterns — Spread-based updates for React/Redux state, why new references trigger re-renders
  5. structuredClone — Modern API, what it handles vs what it doesn't, when to use alternatives