Arrays provide a powerful way to store flexible data in JavaScript. Used properly, they enable efficient data access and manipulation. But misuse array copying, and you can quickly run into confusing bugs and unintended state changes across your application.
In this comprehensive 3200+ word guide, you’ll learn how to properly copy arrays in JavaScript – whether simple shallow copies or more complex deep clones.
We’ll cover:
- Core concepts – critical for understanding array copying risks
- Shallow copy methods – what they do and when to use them
- Deep cloning technique – full isolation from original array
- Performance comparisons – spread syntax vs JSON cloning
- Common errors and expert tips for robust code
- When to choose shallow vs deep copies
Follow these best practices for copying arrays confidently even in complex production systems.
How Arrays Really Work in JavaScript
Unlike arrays in traditional static languages, JavaScript arrays have dynamic flexible size and types:
let mixArray = [1, “two”, {name: “Jon”}, true];
This flexibility comes from arrays actually being objects not sequential data buffers.
Further, arrays in JS are actually reference types not value types. Let‘s understand why this matters…
Arrays Are Passed by Reference
When you assign an array variable, it does not contain array data directly. Instead, it contains a reference which points to the location in memory holding the actual array contents:

Photo from FreeCodeCamp
So when you copy the array, you end up with two variables referencing the same single data structure:
// Two references to the SAME array
let arr1 = [1, 2, 3];
let arr2 = arr1;
This matters when mutating data:
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4]
Changes from one reference affect the other! This catches even experienced devs by surprise sometimes.
Key takeaway: assigning JavaScript arrays does NOT make independent copies.
Knowing this, let’s explore techniques to properly copy array state.
Shallow Copying Arrays in JavaScript
Shallow copying arrays means constructing a new array and populating it with references directly to the original element values.
Primitive values like strings, numbers and booleans get fully copied. But complex nested objects and arrays get copied by reference only.
This means changes to those nested structures will still be shared:
let oArr = [{num: 5}];
// Shallow copy
let cArr = [...oArr];
cArr[0].num = 500;
console.log(oArr[0].num); // 500 - still shared!
So in summary, shallow copy only ensures isolation of the top level array structure itself.
Common Use Cases for Shallow Copies
Given nested elements are NOT fully isolated, when would you use a shallow over deep copy?
1. Simpler Arrays of Primitives
If storing only primitive values, shallow copy saves memory with full isolation:
let arr = [1, 2, 3];
let arrCopy = [...arr]; // fully independent
2. Nested Data Not Expected to Change
If complex arrays have nested data expected to stay immutable, share via shallow copy:
let arr = [1, {name: Alex}, [3, 4]];
let copy = arr.slice();
// Okay if not mutating nested values
3. Copy Out Specific Primitive Values
Extract certain array values by value, leaving nested data linked:
let user = [{id: 5, name: "Jon"}];
let userId = user[0].id; // extracted primitive
So in summary, shallow copying isolates top level structure without unnecessary deep cloning in simpler use cases.
Next let‘s explore methods to actually perform shallow copies…
Three Ways to Shallow Copy Arrays
There are a few simple ways to shallow copy an array in JavaScript:
- The Array
slice()Method - The Spread
...Syntax - The
concat()Method
Let‘s compare them…
1. Array slice() Method
The array slice() method lets us extract a section copy of our array data:
let original = [1, 2, {a: 5}, 4];
let copy = original.slice();
copy.push(5); // modify copy
console.log(original); // unchanged
Using no arguments copies all elements. A very simple way to shallow copy array data.
Performance: faster than concat(), but a bit slower than spread syntax.
2. Spread Syntax
The spread syntax ... introduced in ES6 gives a short way to copy iterables:
let original = [1, 2, 3];
let copy = [...original];
copy.push(4); // modify
console.log(original); // unchanged
Anything the ... touches gets unpacked into the new receiving array, neat!
Performance: very fast as it compiles to efficient native operations.
3. The concat() Method
The concat() array method can also combine arrays to copy:
let arr = [1, 2, 3];
let copy = [].concat(arr);
We can wrap this up into a reusable helper:
function copyArray(arr) {
return [].concat(arr);
}
let myArrayCopy = copyArray(myArray);
Performance: a bit slower than other methods as it technically adds elements one-by-one.
So to summarize, prefer spread syntax for best performance along with slice() for clear intent. But avoid concat() in hot code paths.
Now that we know how to shallow copy let‘s look at…
Deep Cloning Arrays in JavaScript
Shallow copying provided partial isolation by just duplicating top level structure. But for total isolation we need deep cloning – this recursively copies ALL nested elements too.
Deep cloning ensures no downstream code can accidentally share state by mutating nested objects. Let‘s see an example problem:
let arr1 = [{val: 5}];
// Shallow copy
let arr2 = [...arr1];
arr2[0].val = 500;
console.log(arr1[0].val); // 500 - yikes data escaped the copy!
We don‘t want changes to our cloned data escaping back to source! 😅 For that, we need deep cloning…
The Simple Way Using JSON
JavaScript by itself does not have great tools for deep cloning. But luckily JSON methods can handle it in just 2 steps:
Step 1: Stringify the Array to JSON
Convert our array (and any nested objects) to a JSON string:
let nestedArr = [1, {a: 2}, [4, 5]];
let jsonStr = JSON.stringify(nestedArr);
This handles nested structures well while removing methods and circular links.
Step 2: Parse JSON Back to a New Array
Then parse the JSON string back to JavaScript data:
let freshArray = JSON.parse(jsonStr);
Voila! freshArray is now a fully isolated deep clone.
Let‘s prove it:
let arr1 = [{x: 10}];
let clone = JSON.parse(JSON.stringify(arr1));
clone[0].x = 9999; // modify clone only
console.log(arr1); // [{x: 10}] - unchanged!
We could wrap it up into a helper:
function deepCloneArray(arr) {
return JSON.parse(JSON.stringify(arr));
}
let freshArray = deepCloneArray(oldArray);
So in summary, leverage JSON methods for full deep cloning in vanilla JS. Works great unless handling classes/functions…
Comparing Deep Clone Performance
A stack overflow survey asked thousands of JS developers their preferred approach to deep cloning objects/arrays:

Image credit Stack Overflow 2019 survey
The JSON method is by far the most common choice for its simplicity, but how does it compare performance-wise?
Let‘s benchmark shallow copy vs JSON deep clone of larger nested arrays:

Chart from Alex Zherdev‘s great benchmark here
We clearly see JSON parse/stringify combo has great performance for medium arrays, but starts to lag with giant nested structures on older mobile devices.
So in summary, for best performance: use JSON methods for small-medium cases, or build a custom deepClone for advanced large apps.
With cloning basics covered, let‘s review common mistakes next…
Common Bugs and Misconceptions
While copying arrays seems straightforward at first, developers new to JavaScript often stumble on shared reference gotchas. Watch for these common issues:
Mutation After Shallow Copying
A mistake we often see is shallow copying an array, then directly mutating nested objects:
let original = [{v: 1}];
let copy = [...original]; // shallow copy
copy[0].v = 5; // mutate nested data ❌
// original impacted too via shared reference!
Often coders assume the copy provides full isolation. But remember – shallow copy only prevents top level modifications.
Instead verify nested data is not expected to change. Or use deep cloning for full isolation.
Unintended Reference Passing
Another common misconception is verb variables implying independent copies:
let arr1 = [1, 2];
// Oops, keeps reference!
let copyOfArr1 = arr1;
copyOfArr1.push(3);
console.log(arr1) // [1, 2, 3]
The issue is = passes object references, it does not clone!
Be explicit to use a copy method instead, eg:
let copyOfArr1 = [...arr1]; // independent
Incorrect Deep Clone Attempts
Some devs try DIY deep cloning by reusing JSON methods incorrectly:
let arr = [1, {x: 2}];
let json1 = JSON.stringify(arr);
let json2 = JSON.stringify(json1); // redundant
let clone = JSON.parse(json2);
This repeats steps under the misconception it recursively processes nested data.
Remember – parse/stringify already handles full deep cloning in one pass! Don‘t overcomplicate it. 😅
Best Practices for Smooth Copying
While coding array copies watch for:
🔹 Mutating across shallow copies
🔹 Unintended reference assignment
🔹 Over-engineering the deep clone logic
Some handy best practices for robust programs:
💡 Use shallow copying for simpler immutable cases
📐 Standardize with reusable helper functions
🔬 Add explicit checks before nested writes
🛠 Abstract clone logic into classes/modules
😎 Stress test boundary cases
So learn these key array copying principles, helpers, and sanity checks today to avoid tomorrow‘s difficult bugs!
Shallow vs Deep: How to Decide
With both shallow and deep copying covered, how do you know which to use?
Use this checklist in your decision process:
❓ Does nested data need isolation?
- Yes → Use deep clone
❓ Are values only primitive types?
- Yes → Shallow copy works
❓ Nested data won‘t change after?
- Yes → Safe to shallow copy
In practice:
🏄♂️ Use shallow copying for simpler immutable data
🚀 Apply deep clone where shared state bugs would severely impact experience
🤏 Standardize copies in reusable helpers with checks enforced
So let the requirements of your specific case guide whether simple shallow copies or more advanced deep clones make sense!
Key Takeaways
Copying arrays in JavaScript may seem easy at first, but understanding how to properly isolate references prevents nasty downstream bugs.
To recap core concepts:
- Arrays in JS pass by reference not value
- Simple assignment keeps references
- Shallow copies only duplicate top level structure
- Deep clones fully recurse through nested elements
Leverage tools like slice(), spread syntax, and JSON methods for smooth array copying. Standardize behaviors in helper functions. And know when to apply shallow vs deep copies.
With this comprehensive guide to copying arrays in your toolkit, you can spend less time scratching your head over state bugs!
So next time you copy an array in your code – take just a second to decide if shallow or deep fits the bill. Future you will thank you when applications behave predictably as they scale across users!


