Cloning an Array (Shallow and Deep)
Most array clones in product code are shallow, which is exactly what `[...arr]`, `Array.from`, and `slice()` give you. The trap is nested data: shallow copies share inner references, so mutating a nested object inside the copy mutates the original. This snippet walks the modern shallow forms, the older idioms still in the wild, the `structuredClone` deep-copy answer for nested data, and an object-shaped sibling for contrast.
484 views
2
const original = [1, 2, 3, 4];
// Spread is the modern, idiomatic shallow clone.
const spreadCopy = [...original];
// Array.from accepts any iterable and is handy when cloning a Set or NodeList.
const fromCopy = Array.from(original);
spreadCopy.push(5);
console.log(original); // [1, 2, 3, 4] (untouched)
console.log(spreadCopy); // [1, 2, 3, 4, 5]
console.log(fromCopy); // [1, 2, 3, 4]
// Both also clone iterables that are not arrays.
console.log(Array.from(new Set([1, 1, 2, 2, 3]))); // [1, 2, 3]Spread ([...arr]) is the default shallow clone; it works on any iterable and reads as one obvious operation. Array.from does the same for arrays but generalizes to any iterable, which is why it is the right call for cloning a Set, a Map's entries, or a DOM NodeList. Both are shallow: they copy the top-level slot array but share whatever objects sit inside. For a flat array of primitives that is enough; for nested data, see accordion 3.
const items = ['a', 'b', 'c'];
// slice() with no arguments returns a copy of the whole array.
const sliceCopy = items.slice();
// concat() with no arguments does the same.
const concatCopy = items.concat();
sliceCopy.push('d');
console.log(items); // ['a', 'b', 'c']
console.log(sliceCopy); // ['a', 'b', 'c', 'd']
console.log(concatCopy); // ['a', 'b', 'c']
// slice can also clone a sub-range.
console.log(items.slice(1, 3)); // ['b', 'c']These are the pre-spread idioms you will still see in older codebases and in transpiled output. arr.slice() and arr.concat() both return a shallow copy when called with no arguments. They are functionally equivalent to [...arr] but slightly more typing and slightly less obvious to a reader. Prefer spread or Array.from in new code; recognize these forms when reading code written before 2017 or when working with engines that did not yet have spread.
const nested = [
{ id: 1, tags: ['a', 'b'] },
{ id: 2, tags: ['c'] }
];
// Shallow clone shares the inner objects.
const shallow = [...nested];
shallow[0].tags.push('z');
console.log(nested[0].tags); // ['a', 'b', 'z'] (mutation leaked!)
// structuredClone copies the whole graph, including nested arrays/objects.
const deep = structuredClone(nested);
deep[0].tags.push('zz');
console.log(nested[0].tags); // ['a', 'b', 'z'] (untouched by deep mutation)
console.log(deep[0].tags); // ['a', 'b', 'z', 'zz']structuredClone is the right answer for deep-cloning arrays of objects, arrays of arrays, or any tree-shaped data. It is built into Node 17+ and modern browsers, supports cyclic references, and copies most common types (Date, Map, Set, ArrayBuffer, typed arrays). It does NOT copy functions, DOM nodes, or class prototypes (the result is a plain object), which is usually what you want for data clones but can surprise you if you stored a method or class instance. For pure JSON data only, JSON.parse(JSON.stringify(arr)) is a cheaper alternative that loses Date and Map.
const user = { id: 1, name: 'Ada', address: { city: 'London' } };
// Modern: spread.
const spreadUser = { ...user };
// Older: Object.assign starting from a fresh empty object.
const assignUser = Object.assign({}, user);
spreadUser.name = 'Bob';
console.log(user.name); // 'Ada' (top-level untouched)
console.log(spreadUser.name); // 'Bob'
// But the nested address is shared (still a shallow copy).
spreadUser.address.city = 'Paris';
console.log(user.address.city); // 'Paris' (leaked!)
// For deep object clone, reach for structuredClone again.
const deepUser = structuredClone(user);
deepUser.address.city = 'Berlin';
console.log(user.address.city); // 'Paris'
console.log(deepUser.address.city); // 'Berlin'Objects share the same shallow-vs-deep split as arrays. { ...obj } and Object.assign({}, obj) are the two shallow-copy idioms; both share nested objects with the original. The nested-mutation gotcha is the most common shallow-copy bug: the top level looks isolated until a child object's property changes and silently propagates back. The fix is the same as for arrays: structuredClone(obj) deep-copies the whole graph in one call, no library required.
