structuredClone for deep copies
One call, a real deep copy.
Spreading an object copies only the top layer, so nested values stay shared and mutations leak back. structuredClone walks the whole graph for you β nested objects, Maps, Sets, Dates, even cycles β in a single built-in call, no JSON round-trip and its silent data loss.
The shallow-copy trap
{ ...obj } and Object.assign({}, obj) are shallow copies. They duplicate the outer object, but every nested object, array, or Map is copied by reference β the clone and the original point at the same inner values. Mutate one nested field and the "copy" changes too. For flat data that's fine; the moment your data has a second level, a shallow copy is a bug waiting to surprise you.
What structuredClone does
structuredClone(value) is a global function (Node 17+, all modern browsers) that produces a genuine deep copy by recursively walking the value with the structured clone algorithm β the same algorithm that powers postMessage and IndexedDB. Nested objects and arrays are fully duplicated, so the result shares nothing mutable with the source.
Beyond JSON: Maps, Sets, Dates, cycles
The old trick, JSON.parse(JSON.stringify(obj)), only understands JSON. It silently destroys a Map, Set, or RegExp (they become {}), turns a Date into a string, drops undefined, and throws on a cyclic reference. structuredClone handles all of these natively: it preserves Map, Set, Date, ArrayBuffer, typed arrays and more, and it tracks objects it has already seen so a self-referencing graph clones cleanly instead of looping forever.
Its limits
structuredClone copies data, not behaviour. It cannot clone a function, a class method, a DOM node, or a Symbol β any of those throws a DataCloneError. Class instances lose their prototype and come back as plain objects, and a property whose value is a function makes the whole clone fail. When you need those, reach for a domain-specific copy instead; for plain serialisable state, structuredClone is the right default.
Try it 5 examples
The shallow-copy pitfall
JSintroThe spread leaks: writing shallow.nested.retries = 99 also shows 99 on the original because both share one nested object. After structuredClone, setting deep.nested.retries = 1 leaves the original at 99 β the nested level is genuinely its own copy.