How to copy an array in JavaScript (shallow vs deep)

Last reviewed: by
How to copy an array in JavaScript (shallow vs deep)

When you need a javascript array copy or js array copy—to sort, dedupe, or mutate without touching the source—most snippets only perform a shallow duplicate. That is fine for primitives (numbers, strings, booleans): the new array holds its own values, so changing the original’s slots does not rewrite the copy. For objects or nested arrays, shallow methods reuse the same inner references, so a javascript clone array plan must add deep copy tooling when you need true isolation (javascript deep copy array vs javascript shallow copy array).

Tested on: all examples in this tutorial were run with Node.js v20.18.2, and the output shown below each snippet is its exact console output.


Quick reference

Use this table when you are choosing a copy array javascript or copy array js approach: shallow vs deep, when map/filter are appropriate, and when structuredClone or JSON beats spread for nested data.

Approach Typical use
[...arr], arr.slice(), Array.from(arr) Shallow javascript array copy for primitives or when shared objects are intentional
map / filter New array because you are transforming or subsetting, not only duplicating
structuredClone(arr) Deep tree of structured-cloneable data (javascript deep copy array)
JSON.parse(JSON.stringify(...)) JSON-only payloads; know the limitations before you rely on it for javascript clone array trees

1. Spread operator (...)

The spread operator is the most readable copy array javascript pattern for dense arrays: const copy = [...original] creates a new array and copies enumerable elements one level. It is the default people mean by javascript copy array in modern codebases.

javascript
const originalArray = [1, 2, 3];
const copiedArray = [...originalArray];

console.log(copiedArray);

originalArray[0] = 10;
originalArray[1] = 20;
originalArray[2] = 30;

console.log(copiedArray);
console.log(originalArray);
console.log(originalArray === copiedArray);
text
[ 1, 2, 3 ]
[ 1, 2, 3 ]
[ 10, 20, 30 ]
false

2. Array.prototype.slice()

Calling slice() with no arguments is the classic array copy javascript idiom: it returns a new array with shallow copies of the elements in the same order. Behavior on sparse arrays differs from spread—see the sparse section later and the FAQ.

javascript
const originalArray = [1, 2, 3];
const copiedArray = originalArray.slice();

console.log(copiedArray);

originalArray[0] = 3;
originalArray[1] = 4;
originalArray[2] = 6;

console.log(copiedArray);
console.log(originalArray);
text
[ 1, 2, 3 ]
[ 1, 2, 3 ]
[ 3, 4, 6 ]

3. Array.from()

Array.from materializes a new array from an iterable or array-like value—another explicit javascript array copy when you want a real Array instance (for example from a NodeList) or a predictable materialization step in copy array js utilities.

javascript
const originalArray = [1, 2, 3];
const copiedArray = Array.from(originalArray);

console.log(copiedArray);
text
[ 1, 2, 3 ]

4. Array.prototype.concat()

concat with an empty array (or the pattern below) yields a new array with the same shallow references as the source—useful when a pipeline already ends in concat or you prefer a non-spread style for js array copy in older style guides.

javascript
const originalArray = [4, 5, 6];
const copiedArray = [].concat(originalArray);

console.log(copiedArray);

originalArray[0] = 90;
originalArray[1] = 91;
originalArray[2] = 92;

console.log(copiedArray);
text
[ 4, 5, 6 ]
[ 4, 5, 6 ]

5. Array.prototype.map()

map((x) => x) returns a new array and runs your callback per index. For primitives it behaves like other shallow copies; for sparse arrays it is not interchangeable with slice without reading the caveats in the FAQ—prefer spread or slice for a plain javascript shallow copy array unless you are transforming values.

javascript
const originalArray = [1, 2, 3];
const copiedArray = originalArray.map((x) => x);

console.log(copiedArray);

originalArray[0] = 100;

console.log(copiedArray);
text
[ 1, 2, 3 ]
[ 1, 2, 3 ]

6. Array.prototype.filter()

filter returns a new array containing only elements that pass your test—here, even numbers. Surviving entries are still shallow copies of references: object elements point at the same instances as in the source, which matters when people search copy array javascript alongside transform-style APIs.

javascript
const originalArray = [1, 2, 3, 4];
const copiedArray = originalArray.filter((x) => x % 2 === 0);

console.log(copiedArray);
text
[ 2, 4 ]

Shallow copies and nested objects

For objects inside the array, spread, slice, Array.from, map, filter, and concat only copy the outer array container. Inner objects stay shared—both the original and the copy array js view the same mutation unless you deep clone.

javascript
const nested = [{ x: 1 }, { x: 2 }];
const shallowCopy = [...nested];

nested[0].x = 99;

console.log(nested);
console.log(shallowCopy);
text
[ { x: 99 }, { x: 2 } ]
[ { x: 99 }, { x: 2 } ]

Deep copy with structuredClone

For a true javascript deep copy array of plain nested data, structuredClone duplicates nested objects and arrays without the JSON limitations (MDN). It still cannot clone everything (some platform objects or prototypes); when you hit those edges, use libraries or domain-specific logic. A broader object-oriented discussion lives in the JavaScript deep copy guide.

javascript
const nested = [{ x: 1 }, { x: 2 }];
const deepCopy = structuredClone(nested);

nested[0].x = 99;

console.log(nested);
console.log(deepCopy);
text
[ { x: 99 }, { x: 2 } ]
[ { x: 1 }, { x: 2 } ]

Deep copy with JSON.parse(JSON.stringify(...))

JSON serialization is a common hack for javascript clone array trees of JSON-safe data. It drops undefined, functions, Symbol keys, circular graphs, and weakens Date and Map/Set fidelity—so treat it as a narrow tool, not a general deep cloner for copy an array javascript workflows that include rich types.

javascript
const originalArray = [1, 2, [3, 4], { a: 5, b: 6 }];
const copiedArray = JSON.parse(JSON.stringify(originalArray));

console.log(copiedArray);
text
[ 1, 2, [ 3, 4 ], { a: 5, b: 6 } ]

Sparse arrays: spread vs slice vs map

People also search array copy js behavior on sparse arrays (missing indices). On Node v20, spread fills a hole with undefined, while slice preserves the hole, and map skips holes but keeps them empty in the result:

javascript
const sparse = [1, , 3];

console.log([...sparse]);
console.log(sparse.slice());
console.log(sparse.map((x) => x));
text
[ 1, undefined, 3 ]
[ 1, <1 empty item>, 3 ]
[ 1, <1 empty item>, 3 ]

Summary

  • Shallow javascript copy array tools ([...arr], slice, from, concat) give a new outer array but share nested object references—fine for primitives, risky for nested state.
  • Use map / filter when the goal is a derived array, not only a duplicate; check sparse-array semantics against slice/spread.
  • Prefer structuredClone for javascript deep copy array graphs of structured-cloneable plain data.
  • Reserve JSON round-trip for JSON-only payloads; it is not a full javascript clone array solution.
  • On sparse arrays, verify array copy javascript behavior (holes vs undefined) before shipping.

References

MDN and on-site guides for spread, slice, structuredClone, and the wider deep-copy topic behind copy array javascript examples.


Frequently Asked Questions

1. What is the best way to copy an array in JavaScript?

For a shallow copy of a normal dense array, spread ([...arr]) or slice() are common and readable. Use structuredClone(arr) when you need a deep copy of nested objects and arrays (where supported). Avoid JSON.parse(JSON.stringify(...)) as a general deep clone—it drops functions, undefined, special numeric values, prototype chains, circular references, and more.

2. Does copying an array copy the objects inside it?

No. Spread, slice, Array.from, map, filter, and concat create a new array but still reference the same inner objects. Mutating a nested object in one array is visible through the other unless you deep clone.

3. Is `map((x) => x)` the same as copying an array?

It creates a new array and new element slots, but it runs a callback for each index. For sparse arrays, behavior can differ from slice() or spread (holes vs undefined). Prefer spread or slice for a plain shallow copy unless you are transforming values.

4. Do spread and slice behave the same on sparse arrays?

Not always. Spread tends to turn holes into undefined in the new array, while slice can preserve holes. map skips holes but preserves them in the result. Prefer explicit Array.from with a mapper or document the behavior you rely on.
Olorunfemi Akinlua

Boasting over five years of experience in JavaScript, specializing in technical content writing and UX design. With a keen focus on programming languages, he crafts compelling content and designs user-friendly interfaces to enhance digital …

  • JavaScript
  • Web Design