JS Spread Operator for Arrays: Complete Tutorial

Learn every use of the spread operator (...) with arrays: copying, merging, inserting elements, converting iterables, and passing array elements as function arguments.

6 min read

The spread operator looks like three dots: .... When used with an array, it expands the array's elements into individual values wherever a list of values is expected. It was introduced in ES6 and quickly became one of the most used features in modern JavaScript.

The spread operator does not mutate anything. It reads elements from an array and places them into a new context -- a new array literal, a function call, or another iterable position.

Copying an Array

The most basic use of spread is creating a shallow copy of an array. Wrapping an existing array in a new array literal with the spread syntax in front of it produces a brand new array containing the same elements:

javascriptjavascript
const original = [1, 2, 3];
 
const copy = [...original];
 
console.log(copy);     // [1, 2, 3]
console.log(copy === original); // false (different array objects)

This replaces the older original.slice() pattern. It is shorter and the intent is clearer, since you can see immediately that a copy is being made rather than reading past an unrelated-looking method name.

Because it is a shallow copy, changing the copy does not affect the original, at least as long as the elements are plain values rather than objects:

javascriptjavascript
const fruits = ["apple", "banana"];
 
const copy = [...fruits];
copy.push("orange");
 
console.log(fruits); // ["apple", "banana"] (unchanged)
console.log(copy);   // ["apple", "banana", "orange"]

But if the array contains objects, the objects themselves are shared. Modifying an object in the copy also changes the original.

Merging Arrays

Spread is the cleanest way to combine multiple arrays:

javascriptjavascript
const part1 = [1, 2];
const part2 = [3, 4];
const part3 = [5, 6];
 
const all = [...part1, ...part2, ...part3];
 
console.log(all); // [1, 2, 3, 4, 5, 6]

You control the order completely, since elements appear in exactly the sequence you write the spreads, regardless of which array they originally came from:

javascriptjavascript
const early = ["morning"];
const late = ["evening"];
 
console.log([...late, ...early]);  // ["evening", "morning"]
console.log([...early, ...late]);  // ["morning", "evening"]

For a full comparison of merging techniques including concat() and push() with spread, see How to Merge Two Arrays in JavaScript.

Inserting Elements at Any Position

Spread lets you insert elements at the beginning, middle, or end of a new array without mutating:

javascriptjavascript
const middle = [2, 3];
 
// Insert at the beginning
console.log([1, ...middle]);     // [1, 2, 3]
 
// Insert at the end
console.log([...middle, 4]);     // [2, 3, 4]
 
// Insert on both sides
console.log([1, ...middle, 4]);  // [1, 2, 3, 4]

This is much cleaner than using unshift() and push() separately, and it does not mutate the original array.

Converting Iterables to Arrays

Spread works on any iterable, not just arrays. This means you can convert strings, Sets, Maps, and NodeLists into arrays:

javascriptjavascript
// String to array of characters
console.log([..."hello"]); // ["h", "e", "l", "l", "o"]
 
// Set to array (removes duplicates automatically)
const unique = [...new Set([1, 2, 2, 3, 3, 4])];
console.log(unique); // [1, 2, 3, 4]
 
// NodeList to array (DOM elements)
// const buttons = [...document.querySelectorAll("button")];

Array.from() does the same thing and is sometimes clearer for these conversions, but spread is shorter when you are already inside an array literal.

Passing Array Elements as Function Arguments

Before spread, if you had an array of values and wanted to pass them as separate arguments to a function, you used Function.prototype.apply(). Spread makes this straightforward:

javascriptjavascript
const scores = [85, 92, 78, 95, 88];
 
const highest = Math.max(...scores);
console.log(highest); // 95
 
const lowest = Math.min(...scores);
console.log(lowest); // 78

Math.max() expects individual numbers, not an array. The spread operator unpacks the array so each element becomes a separate argument.

This also works with your own functions:

javascriptjavascript
function introduce(first, second, third) {
  console.log(`${first}, meet ${second} and ${third}.`);
}
 
const names = ["Alice", "Bob", "Carol"];
 
introduce(...names);
// "Alice, meet Bob and Carol."

Spread vs Other Array Operations

TaskOld WaySpread Way
Copy arrayarr.slice()[...arr]
Merge arraysa.concat(b)[...a, ...b]
Array to argumentsMath.max.apply(null, arr)Math.max(...arr)
String to arraystr.split("")[...str]

Spread is not always a drop-in replacement, concat() can take mixed values and arrays in one call, and slice() can extract a portion instead of copying the whole array. But for the common cases listed above, spread reads more clearly and takes less typing than the older alternatives it replaces.

Common Mistakes

Confusing spread with rest parameters. They use the same ... syntax but do opposite things, and mixing them up is a common source of confusion for anyone new to modern JavaScript syntax:

javascriptjavascript
// REST: collects arguments into an array (in function definition)
function logAll(...items) {
  console.log(items); // ["a", "b", "c"]
}
logAll("a", "b", "c");
 
// SPREAD: expands an array into arguments (in function call)
const arr = ["x", "y", "z"];
logAll(...arr); // ["x", "y", "z"]

Rest packs values together. Spread unpacks them again.

Expecting spread to deep-copy. If your array contains objects, the copy shares references to the same inner objects:

javascriptjavascript
const original = [{ name: "Alice" }, { name: "Bob" }];
const copy = [...original];
 
copy[0].name = "Changed";
 
console.log(original[0].name); // "Changed" (surprising!)

For immutable updates to nested data, see Copying Nested Objects with the JS Spread Operator.

Now that you understand spread, the next step is learning the array iteration methods that replace traditional loops. Start with map vs forEach to see how spread patterns combine with functional array methods.

Rune AI

Rune AI

Key Insights

  • The spread operator ... expands an array into individual elements inside a new context.
  • Use [...arr] to create a shallow copy of an array.
  • Use [...a, ...b] to merge arrays without mutating the originals.
  • Use [...arr, newItem] to insert elements at any position.
  • Spread works on any iterable: arrays, strings, Sets, Maps, and NodeLists.
RunePowered by Rune AI

Frequently Asked Questions

Does the spread operator create a deep copy?

No. The spread operator creates a shallow copy. If the array contains objects or nested arrays, those inner references are shared between the original and the copy. For deep cloning, use structuredClone().

What is the difference between spread and rest parameters?

Spread expands an array into individual elements (used when calling a function or building a new array). Rest collects individual arguments into an array (used in function parameter definitions). They use the same ... syntax but in opposite directions.

Can I spread a string?

Yes. Spreading a string splits it into individual characters: [...'hello'] produces ['h', 'e', 'l', 'l', 'o']. This works because strings are iterable.

Conclusion

The spread operator is one of the most useful additions to modern JavaScript. It replaces multiple older patterns -- concat() for merging, slice() for copying, and apply() for function arguments -- with a single clean syntax. Once you start using spread, you will reach for it constantly when working with arrays.