Skip to content
JavaScript js arrays 4 min read

Spread with Arrays

The spread operator (...) expands an iterable — most commonly an array — into individual elements wherever a list of values is expected. Introduced in ES2015, it has become the idiomatic way to clone arrays, merge them, insert elements, and forward arguments to functions, all without mutating the originals. Because it favors creating new arrays over modifying existing ones, spread fits naturally into modern, immutable-by-default JavaScript. The one thing to keep in mind is that it copies shallowly — a caveat we cover at the end.

Cloning an array

Spreading an array into a new array literal produces a fresh array with the same elements. This is the cleanest way to copy an array so you can modify the copy without touching the source.

const original = [1, 2, 3];
const copy = [...original];

copy.push(4);

console.log(original); // unchanged
console.log(copy);
console.log(original === copy); // different references

Output:

[ 1, 2, 3 ]
[ 1, 2, 3, 4 ]
false

This is equivalent to original.slice() but reads more declaratively, and it works on any iterable, not just arrays.

Merging and concatenating

Spread multiple arrays into one literal to concatenate them. Unlike Array.prototype.concat, you can freely interleave spread arrays with standalone values and control exactly where each piece lands.

const front = ["a", "b"];
const back = ["d", "e"];

const merged = [...front, "c", ...back];

console.log(merged);

Output:

[ 'a', 'b', 'c', 'd', 'e' ]

Inserting elements anywhere

Because spread is just a value expression inside an array literal, you can insert new elements at the start, middle, or end while keeping the result immutable.

const items = ["apple", "cherry"];

const withBanana = [...items.slice(0, 1), "banana", ...items.slice(1)];

console.log(withBanana);

Output:

[ 'apple', 'banana', 'cherry' ]

Spreading into function arguments

Spread can pass an array’s elements as separate positional arguments to a function. This replaces the old Function.prototype.apply pattern and works with constructors too.

const numbers = [5, 12, 3, 27, 8];

console.log(Math.max(...numbers));
console.log(Math.min(...numbers));

// Old way, no longer needed:
// Math.max.apply(null, numbers);

// Works with constructors:
const parts = [2026, 5, 1];
const date = new Date(...parts);
console.log(date.getFullYear());

Output:

27
3
2026

Tip: Don’t spread very large arrays into function calls. Each element becomes a separate argument, and engines cap how many arguments a call can take (often ~65k–500k depending on the runtime). For large datasets, use a method like reduce instead.

Turning a string into an array

Strings are iterable, so spreading one yields an array of characters. Crucially, spread iterates by Unicode code point, so it handles astral characters (like many emoji) correctly where String.prototype.split("") does not.

const text = "café";

console.log([...text]);
console.log([..."🚀⭐"]);
console.log("🚀⭐".split("")); // breaks surrogate pairs

Output:

[ 'c', 'a', 'f', 'é' ]
[ '🚀', '⭐' ]
[ '\ud83d', '\ude80', '⭐' ]

Spread vs. concat vs. push

These approaches overlap but differ in mutation behavior and ergonomics.

TechniqueMutates source?ReturnsBest for
[...a, ...b]NoNew arrayImmutable merges, inserts, clones
a.concat(b)NoNew arraySimple two-array joins, older codebases
a.push(...b)Yes (a)New lengthAppending into an existing array in place

The shallow-copy caveat

Spread copies only one level deep. Elements that are objects or nested arrays are copied by reference, so mutating a nested value through the copy also affects the original.

const users = [{ name: "Ada" }, { name: "Linus" }];
const cloned = [...users];

cloned[0].name = "Grace"; // mutates the shared object

console.log(users[0].name); // affected!
console.log(cloned === users); // top level is a real copy

Output:

Grace
false

For a fully independent copy of nested data, use structuredClone:

const deepCopy = structuredClone(users);
deepCopy[0].name = "Margaret";

console.log(users[0].name); // safe

Output:

Grace

Warning: structuredClone cannot copy functions, DOM nodes, or class instances (it throws a DataCloneError). For plain data it’s ideal; for richer objects, copy the relevant fields explicitly.

Best Practices

  • Reach for [...arr] to clone or merge arrays immutably instead of mutating with push/splice.
  • Remember spread is shallow — use structuredClone when you need independent nested objects.
  • Prefer fn(...args) over fn.apply(null, args) for forwarding arguments; it’s clearer and supports constructors.
  • Use [...str] rather than str.split("") when the string may contain emoji or other multi-byte characters.
  • Avoid spreading huge arrays into function calls; iterate or reduce instead to stay within argument limits.
  • Combine spread with destructuring (const [first, ...rest] = arr) to split arrays cleanly.
Last updated June 1, 2026
Was this helpful?