Glossary / Defensive copying
Defensive copying
Defensive copying creates a separate representation of data at an ownership boundary so callers cannot mutate internal state through shared references, or internal changes cannot unexpectedly affect callers. It is useful for configuration, caches, immutable APIs, and trust boundaries. The correct copying depth and supported types depend on the contract; copying everything recursively is neither free nor universally well-defined.
Ownership and copy choices
function makeOptions(input) {
return { ...input }; // protects only the outer object
}
The example is shallow: nested objects remain shared. A deep copy recursively duplicates nested structures, but must define behavior for cycles, class instances, functions, Maps/Sets, typed arrays, accessors, Symbols, and shared-reference topology. structuredClone supports many built-ins and cycles, but not functions or arbitrary prototype behavior. JSON round-tripping supports only a restricted JSON-shaped subset and can lose undefined, special numbers, Symbols, prototypes, and identity.
Defensive copying is especially important when accepting mutable objects from plugins or consumers. Validate and normalize input first, then retain only the fields the component needs. At output boundaries, return immutable snapshots or copies if callers must not mutate internal state. For very large data, ownership transfer, persistent structures, read-only interfaces, or targeted copies may be better.
Interview questions
- Why copy at an API boundary? To avoid aliasing that permits unintended mutation across ownership boundaries.
- What does shallow copying leave shared? Nested referenced objects.
- Is structuredClone universal? No; it supports defined types but not functions or arbitrary custom prototypes.
- What are alternatives to deep cloning? Clear ownership, immutable data, targeted copying, or transfer of ownership.