Question

Copy Arrays by Value in JavaScript: Shallow and Deep Copies

javascriptarraysdeep-copypass-by-value

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.

Take Quiz

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

const users1 = [{ name: "Ada", active: true }];
const users2 = [...users1];

users2[0].active = false;

console.log(users1[0].active); // false

The arrays are different, but users1[0] and users2[0] refer to the same object. For plain data that can be serialized, structuredClone() makes a deep copy:

const users3 = structuredClone(users1);
users3[0].active = true;

console.log(users1[0].active); // false
console.log(users3[0].active); // true

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);
  1. arr1 is created as an array containing "a", "b", and "c".
  2. [...arr1] reads each top-level element from arr1 and creates a new array containing those elements.
  3. arr2 refers to that new array, not to arr1.
  4. arr2.push("d") modifies only the array referred to by arr2.
  5. The output is:
["a", "b", "c"]
["a", "b", "c", "d"]

Compare it with direct assignment:

const arr1 = ["a", "b", "c"];
const arr2 = arr1;

Here, no new array is created. Both variables refer to the same array, so a mutation through one variable is visible through the other.

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() because sort() 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"]
Take Quiz

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) {
    return user;
  }

  return { ...user, active: false };
});

This pattern preserves unchanged user objects, creates a new array, and creates a new object only for the user being updated.

Clone plain nested data when necessary

const requestOptions = structuredClone(defaultOptions);
requestOptions.headers.authorization = token;

Use deep cloning thoughtfully. It can be unnecessary or expensive when only one small nested part needs changing; targeted copies with spread syntax are often clearer.

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

const copy = JSON.parse(JSON.stringify(original));

This may work for simple JSON-like data, but it loses or changes values such as undefined, Date, Map, Set, functions, and special numeric values. Prefer structuredClone() for cloneable data, or create targeted copies based on your data structure.

Copying after mutation

items.push(newItem);
const copy = [...items];

This preserves a copy of the already-mutated array. If you need the old value unchanged, copy first or build the new value directly:

const updatedItems = [...items, newItem];

Forgetting that some methods mutate

push(), pop(), shift(), unshift(), splice(), sort(), and reverse() mutate an array. Methods such as map(), filter(), and slice() return new arrays.

Comparisons

ApproachCreates a new array?Copies nested objects independently?Best use
arr2 = arr1NoNoIntentionally share one array
[...arr1]YesNoStandard shallow copy
arr1.slice()YesNoShallow copy, including older codebases
Array.from(arr1)YesNoCopy arrays or convert iterable values
arr1.map(item => item)YesNoTransform elements; not needed for a plain copy
structuredClone(arr1)YesUsually yes for cloneable valuesIndependent nested plain data

Shallow copy versus deep copy

Copy typeWhat is independent?Example
Shallow copyThe outer arrayconst copy = [...original]
Deep copyThe array and its cloneable nested dataconst copy = structuredClone(original)

A shallow copy is usually the right default. Use a deep copy only when nested values must also be independent.

Take Quiz

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 reverse.

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:

console.log(arr1 === arr2);

It is true when both variables reference the same array object.

Take Quiz

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

  1. Start with an array of task objects containing id, title, and done properties.
  2. Add a task without using push() on the original array.
  3. Mark one task as complete without changing the original task object.
  4. Remove a task by its id.
  5. Return a sorted copy of the tasks by title.
  6. Log the original list and each updated result to verify that the original remains unchanged.
Take Quiz

Keep learning

Related questions

View more