Question
Copy Arrays by Value in JavaScript: Shallow and Deep Copies
Question
When assigning one JavaScript array to another variable, the new variable refers to the same array rather than an independent copy:
var arr1 = ["a", "b", "c"];
var arr2 = arr1;
arr2.push("d");
// arr1 is now ["a", "b", "c", "d"]
How can you copy an array so that arr1 and arr2 are independent arrays, and changing one does not change the other?
Short Answer
You will learn why arr2 = arr1 shares an array reference, how to make a new array with the spread operator, slice(), or Array.from(), and when a deep copy is needed for nested data.
Concept
JavaScript variables store values. For primitive values such as strings, numbers, and booleans, assignment copies the value directly. Arrays are objects, so assignment copies a reference to the array instead.
const arr1 = ["a", "b", "c"];
const arr2 = arr1;
After this code, arr1 and arr2 both point to one array in memory. Calling a mutating method such as push(), pop(), splice(), sort(), or reverse() through either variable changes that shared array.
To make an independent top-level array, create a new array containing the same elements:
const arr2 = [...arr1];
This is called a shallow copy. It copies the array container, which is sufficient when its elements are primitives. If the array contains objects or other arrays, those nested values are still shared references. In that case, choose a deliberate deep-copy strategy when you truly need independent nested data.
Mental Model
Think of an array as a storage box and a variable as a note containing the box's address.
const arr2 = arr1;
This does not build a second box. It writes the same address on a second note, so both notes lead to one box.
const arr2 = [...arr1];
This creates a new box and puts copies of the top-level items into it. For strings such as "a", that is fully independent. However, if an item is itself an object, the new box receives another note pointing to the same nested object.
Syntax and Examples
The modern, common way to copy an array is the spread operator:
const arr1 = ["a", "b", "c"];
const arr2 = [...arr1];
arr2.push("d");
console.log(arr1); // ["a", "b", "c"]
console.log(arr2); // ["a", "b", "c", "d"]
[...arr1] creates a new array and expands the elements of arr1 into it.
Other shallow-copy options are:
const arr1 = ["a", "b", "c"];
const copyWithSlice = arr1.slice();
const copyWithArrayFrom = Array.from(arr1);
const copyWithConcat = [].concat(arr1);
All four options create a new top-level array. For a straightforward array copy, prefer spread syntax because it is concise and easy to read.
Copying an array with nested objects
users1 = [{ : , : }];
users2 = [...users1];
users2[]. = ;
.(users1[].);
Step by Step Execution
Trace this shallow-copy example:
const arr1 = ["a", "b", "c"];
const arr2 = [...arr1];
arr2.push("d");
console.log(arr1);
console.log(arr2);
arr1is created as an array containing"a","b", and"c".[...arr1]reads each top-level element fromarr1and creates a new array containing those elements.arr2refers to that new array, not toarr1.arr2.push("d")modifies only the array referred to byarr2.- The output is:
["a", "b", "c"]
["a", "b", , ]
Real World Use Cases
- UI state updates: Copy an array before adding or removing an item so existing state is not mutated unexpectedly.
- API data processing: Keep the original response data while creating a modified list for display.
- Sorting results: Copy an array before calling
sort()becausesort()changes its original array. - Undo or history snapshots: Store a copy of a list before applying a user action.
- Function inputs: Copy an input array when a function needs to modify a local version without surprising its caller.
Example: sorting product names without changing the original order:
const products = ["Mouse", "Keyboard", "Adapter"];
const sortedProducts = [...products].sort();
console.log(products); // ["Mouse", "Keyboard", "Adapter"]
console.log(sortedProducts); // ["Adapter", "Keyboard", "Mouse"]
Real Codebase Usage
Developers often avoid direct mutation, particularly when working with state, shared configuration, or function parameters.
Make an updated array without changing the old one
function addTodo(todos, newTodo) {
return [...todos, newTodo];
}
This function returns a new array. The caller's todos array remains unchanged.
Remove items with filter()
function removeTodo(todos, id) {
return todos.filter((todo) => todo.id !== id);
}
filter() returns a new array, so it is useful for immutable updates.
Update one object inside an array
A shallow array copy alone is not enough when changing an object in the array. Copy both the array and the changed object:
const updatedUsers = users.map((user) => {
if (user.id !== targetId) {
user;
}
{ ...user, : };
});
Common Mistakes
Expecting assignment to copy an array
const copy = original; // Not a copy
This creates a second reference to the same array. Use const copy = [...original] for a new top-level array.
Assuming a shallow copy duplicates nested objects
const original = [{ score: 10 }];
const copy = [...original];
copy[0].score = 20;
console.log(original[0].score); // 20
Avoid this by copying the object you intend to change:
const copy = original.map((item) => ({ ...item }));
For deeper nested plain data, use structuredClone(original) where supported and appropriate.
Using JSON.parse(JSON.stringify(...)) as a universal deep clone
copy = .(.(original));
Comparisons
| Approach | Creates a new array? | Copies nested objects independently? | Best use |
|---|---|---|---|
arr2 = arr1 | No | No | Intentionally share one array |
[...arr1] | Yes | No | Standard shallow copy |
arr1.slice() | Yes | No | Shallow copy, including older codebases |
Array.from(arr1) | Yes | No | Copy arrays or convert iterable values |
arr1.map(item => item) | Yes | No |
Cheat Sheet
// Direct assignment: shared reference
const alias = original;
// Shallow copies: new outer array
const copy1 = [...original];
const copy2 = original.slice();
const copy3 = Array.from(original);
const copy4 = [].concat(original);
// Add without mutation
const updated = [...original, newItem];
// Remove without mutation
const updated = original.filter((item) => item !== unwantedItem);
// Sort without mutation
const sorted = [...original].sort();
// Deep-copy cloneable nested data
const deepCopy = structuredClone(original);
===checks whether two variables refer to the same object or array.[...array]copies only the outer array.- Objects and arrays inside a shallow copy are still shared.
- Mutating methods include
push,pop,splice,sort, and .
FAQ
Does arr2 = arr1 copy an array in JavaScript?
No. It copies the reference, so both variables refer to the same array.
What is the easiest way to copy an array in JavaScript?
Use the spread operator:
const copy = [...original];
Is [...array] a deep copy?
No. It is a shallow copy. Nested objects and arrays remain shared.
How do I deep copy an array in JavaScript?
For cloneable data, use:
const deepCopy = structuredClone(original);
For complex application data, targeted copying is often more appropriate than cloning everything.
Does slice() modify the original array?
No. slice() returns a new array and does not change the original.
Why does sorting one array change another variable's array?
If both variables were created with assignment, they share one array. Also, sort() mutates the array. Use [...array].sort() to sort a copy.
How can I tell whether two variables share the same array?
Use strict equality:
Mini Project
Description
Build a small task-list update utility. It demonstrates how to add, remove, sort, and update task data while preserving the original array and nested task objects.
Goal
Create functions that return updated task arrays without mutating the original task list.
Requirements
- Start with an array of task objects containing
id,title, anddoneproperties. - Add a task without using
push()on the original array. - Mark one task as complete without changing the original task object.
- Remove a task by its
id. - Return a sorted copy of the tasks by title.
- Log the original list and each updated result to verify that the original remains unchanged.
Keep learning
Related questions
Abort Ajax Requests with jQuery jqXHR.abort()
Learn how to cancel an in-progress jQuery Ajax request with jqXHR.abort(), handle abort status safely, and avoid stale UI updates.
Access the Correct this Inside a JavaScript Callback
Learn why JavaScript this changes in callbacks and how to preserve an object context using bind, arrow functions, and event handler patterns.
Add Key-Value Pairs to JavaScript Objects
Learn how to add key-value pairs to JavaScript objects with dot and bracket notation, dynamic keys, examples, and common mistakes.