Objects are reference types — assigning or passing them shares the same memory reference. Shallow copies duplicate top-level properties while deep copies (structuredClone) create fully independent clones at all nesting levels.
Objects are stored by reference — assigning or passing an object shares the same memory location, so mutations affect all references.
Spread operator, Object.assign, and array methods (slice, from) copy top-level properties but nested objects still share references.
Built-in ES2022 method that creates fully independent clones, handling circular refs, Date, Map, and Set — but cannot clone functions or DOM nodes.
freeze() prevents all property changes; seal() prevents add/remove but allows modification. Both are shallow — nested objects remain mutable.
=== compares object references, not content. Two objects with identical properties are not equal unless they point to the same memory location.
Objects and arrays in JavaScript are reference types — variables hold a reference (pointer) to the object in memory, not the object itself. When you assign an object to another variable or pass it to a function, both variables point to the same object. Mutating one affects the other. Understanding this behavior and how to create independent copies is a fundamental interview topic.
A shallow copy creates a new object and copies the top-level properties. Primitive values (strings, numbers, booleans) are duplicated, but nested objects and arrays still share the same references as the original. Modifying a nested property in the copy will also modify the original.
Shallow copy methods:
{ ...obj } for objects, [ ...arr ] for arraysObject.assign({}, obj) — copies enumerable own properties from source to targetArray.from(arr), arr.slice(), arr.concat() — all produce shallow copies of arraysA deep copy creates a completely independent clone at all nesting levels. No references are shared between the original and the copy.
structuredClone(value) (ES2022) is the modern built-in solution. It handles circular references, Date, Map, Set, ArrayBuffer, RegExp, and typed arrays correctly. Limitations: it cannot clone functions, DOM nodes, Error objects, or property descriptors (getters/setters are evaluated and the result value is copied). Throws a DataCloneError for non-cloneable types.
JSON.parse(JSON.stringify(obj)) is the legacy workaround. It works for plain data objects but has significant data loss: functions are silently dropped, undefined values are stripped, Date objects become ISO strings, NaN and Infinity become null, Map/Set become empty objects, and circular references throw a TypeError. Use structuredClone instead whenever possible.
These methods control mutability but do not create copies:
Object.freeze(obj) — prevents adding, removing, and modifying properties. In strict mode, mutation attempts throw TypeError; in non-strict mode, they silently fail. Critically, freeze is shallow — nested objects remain fully mutable. To deep-freeze, you must recursively freeze all nested objects.Object.seal(obj) — prevents adding and removing properties but allows modifying existing property values. Useful when you want to protect the object's structure while allowing value updates.Object.isFrozen() and Object.isSealed() check the current state.Every object property has a descriptor with value, writable, enumerable, and configurable flags. Object.defineProperty() creates properties with fine-grained control. Object.getOwnPropertyDescriptors() is useful for copying properties with their full descriptors (not just values), which Object.assign does not preserve.
=== compares references, not content — two objects with identical properties are not equal unless they are the same object in memory. For deep equality comparison, use a library function or write a recursive comparison. JSON.stringify comparison works for simple objects but fails on key order differences and non-serializable values.
Shallow copy: new container, shared nested references. Deep copy: fully independent at all levels. structuredClone is the modern standard for deep cloning. Object.freeze prevents mutation but is shallow. Always consider whether your code needs a copy or just a reference — unnecessary deep copies waste memory and CPU.
Fun Fact
The JSON.parse(JSON.stringify()) deep clone trick has been used since 2009, but it took 13 years for JavaScript to get a proper built-in alternative — structuredClone was standardized in 2022, based on an algorithm that web browsers had been using internally since 2010 for postMessage() and IndexedDB.