JS Spread Operator for Arrays: Complete Tutorial

Master the JavaScript spread operator for arrays. Learn how to copy arrays, merge arrays, add elements, convert iterables, use spread in function arguments, and avoid common pitfalls with practical code examples.

JavaScriptbeginner
14 min read

The spread operator (...) expands an array into its individual elements. It is one of the most versatile features introduced in ES6, enabling clean array copying, merging, element insertion, and function argument spreading. Instead of writing manual loops or using older methods like concat() and apply(), the spread operator gives you a concise, readable syntax for all these operations.

What the Spread Operator Does

The spread operator unpacks an array into separate values:

javascriptjavascript
const numbers = [1, 2, 3];
 
// Without spread: the array itself is passed
console.log(numbers);     // [1, 2, 3]
 
// With spread: individual elements are unpacked
console.log(...numbers);  // 1 2 3
 
// Equivalent to:
console.log(1, 2, 3);    // 1 2 3

Think of ... as "take everything out of the brackets and lay it flat."

Copying Arrays

The spread operator creates a shallow copy of an array. This is the modern replacement for .slice():

javascriptjavascript
const original = [10, 20, 30, 40];
const copy = [...original];
 
copy.push(50);
 
console.log(original); // [10, 20, 30, 40] -- unaffected
console.log(copy);     // [10, 20, 30, 40, 50]
Copy MethodSyntaxReadability
Spread operator[...arr]Most readable, modern standard
slice()arr.slice()Classic, slightly less clear intent
Array.from()Array.from(arr)Works with any iterable
concat()[].concat(arr)Older pattern, less intuitive

Shallow Copy Warning

Spread creates a shallow copy. Nested arrays and objects inside the array are still shared references:

javascriptjavascript
const matrix = [[1, 2], [3, 4]];
const shallowCopy = [...matrix];
 
// Modifying a nested array affects both
shallowCopy[0].push(99);
console.log(matrix[0]);      // [1, 2, 99] -- also changed!
 
// Fix: use structuredClone for deep copies
const deepCopy = structuredClone(matrix);
deepCopy[0].push(100);
console.log(matrix[0]);      // [1, 2, 99] -- unaffected by deep copy change

Merging Arrays

Combining two or more arrays is one of the most common spread operator use cases. For a detailed guide, see how to merge arrays in JavaScript.

javascriptjavascript
const frontend = ["HTML", "CSS", "JavaScript"];
const backend = ["Node.js", "Python", "Go"];
 
const fullStack = [...frontend, ...backend];
console.log(fullStack);
// ["HTML", "CSS", "JavaScript", "Node.js", "Python", "Go"]
 
// Merge three arrays
const databases = ["PostgreSQL", "MongoDB"];
const allTech = [...frontend, ...backend, ...databases];
console.log(allTech.length); // 8

Merge with New Elements

javascriptjavascript
const existing = ["task 1", "task 2"];
const updated = [...existing, "task 3", "task 4"];
console.log(updated);
// ["task 1", "task 2", "task 3", "task 4"]
 
// Insert at the beginning
const withPriority = ["urgent task", ...existing];
console.log(withPriority);
// ["urgent task", "task 1", "task 2"]

Inserting Elements at a Specific Position

You can combine slice with spread to insert elements at any index without mutating the original:

javascriptjavascript
const colors = ["red", "blue", "purple"];
 
// Insert "green" and "yellow" at index 1
const updated = [
  ...colors.slice(0, 1),
  "green",
  "yellow",
  ...colors.slice(1)
];
 
console.log(updated);
// ["red", "green", "yellow", "blue", "purple"]
 
// Original is unchanged
console.log(colors);
// ["red", "blue", "purple"]

Compare this to using splice, which mutates the original array:

javascriptjavascript
const colorsMut = ["red", "blue", "purple"];
colorsMut.splice(1, 0, "green", "yellow"); // Mutates in place
console.log(colorsMut);
// ["red", "green", "yellow", "blue", "purple"]

Spread in Function Arguments

Use spread to pass array elements as individual arguments to a function:

javascriptjavascript
const scores = [88, 92, 75, 100, 67];
 
// Without spread: use apply (old way)
const maxOld = Math.max.apply(null, scores);
 
// With spread: clean and readable
const max = Math.max(...scores);
const min = Math.min(...scores);
 
console.log(max); // 100
console.log(min); // 67

Real-World Example: Logging with Context

javascriptjavascript
function logWithTimestamp(level, ...messageParts) {
  const timestamp = new Date().toISOString();
  console.log(`[${timestamp}] [${level}]`, ...messageParts);
}
 
logWithTimestamp("INFO", "User", "Alice", "logged in");
// [2026-03-05T14:10:00.000Z] [INFO] User Alice logged in
 
logWithTimestamp("ERROR", "Failed to fetch", "/api/users", "Status: 500");
// [2026-03-05T14:10:00.000Z] [ERROR] Failed to fetch /api/users Status: 500

Converting Iterables to Arrays

Spread works with any iterable, not just arrays:

javascriptjavascript
// String to array of characters
const chars = [..."Hello"];
console.log(chars); // ["H", "e", "l", "l", "o"]
 
// Set to array (removes duplicates)
const unique = [...new Set([1, 2, 2, 3, 3, 3])];
console.log(unique); // [1, 2, 3]
 
// Map entries to array
const map = new Map([["a", 1], ["b", 2]]);
const entries = [...map];
console.log(entries); // [["a", 1], ["b", 2]]
 
// NodeList to array (in browser)
// const divs = [...document.querySelectorAll("div")];

For a dedicated guide to removing duplicate values, see removing duplicates from arrays.

Spread with Destructuring

Combine spread with array destructuring to separate the first elements from the rest:

javascriptjavascript
const scores = [95, 88, 76, 82, 91];
 
const [highest, secondHighest, ...remaining] = scores;
 
console.log(highest);       // 95
console.log(secondHighest); // 88
console.log(remaining);     // [76, 82, 91]

Swapping Variables

javascriptjavascript
let a = "first";
let b = "second";
 
[a, b] = [b, a];
 
console.log(a); // "second"
console.log(b); // "first"

Spread vs. Rest

The ... syntax serves two purposes depending on context:

ContextNameWhat It DoesExample
In array literals or function callsSpreadExpands an array into individual elements[...arr], fn(...args)
In function parameters or destructuringRestCollects individual elements into an arrayfunction(...args), const [a, ...rest] = arr
javascriptjavascript
// SPREAD: expanding into a new array
const merged = [...[1, 2], ...[3, 4]]; // [1, 2, 3, 4]
 
// REST: collecting into a new variable
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

Common Mistakes

Spreading Non-Iterables

javascriptjavascript
// Bug: numbers and objects are not iterable
const num = 42;
// const arr = [...num]; // TypeError: num is not iterable
 
// Fix: only spread iterables (arrays, strings, Sets, Maps)
const str = "42";
const arr = [...str]; // ["4", "2"]

Expecting Deep Copies

javascriptjavascript
const users = [
  { name: "Alice", scores: [90, 85] },
  { name: "Bob", scores: [88, 92] }
];
 
// Shallow copy: nested objects are shared
const copy = [...users];
copy[0].scores.push(100);
 
console.log(users[0].scores); // [90, 85, 100] -- original mutated!
 
// Fix: deep copy when needed
const deepCopy = structuredClone(users);

Performance with Very Large Arrays

javascriptjavascript
// Avoid spreading in tight loops with massive arrays
const huge = new Array(1_000_000).fill(0);
 
// This creates a new million-element array on every iteration
// for (let i = 0; i < 100; i++) {
//   huge = [...huge, i]; // Very slow: O(n) copy each time
// }
 
// Better: use push for accumulation
for (let i = 0; i < 100; i++) {
  huge.push(i); // O(1) amortized
}

Best Practices

  1. Use spread for immutable updates. When you need a new array without mutating the original, [...arr, newItem] is cleaner than .concat().
  2. Prefer spread over apply. Math.max(...arr) reads better than Math.max.apply(null, arr).
  3. Use structuredClone for nested data. Spread only creates shallow copies; nested references are shared.
  4. Combine with Set for unique values. [...new Set(arr)] is the most concise way to remove duplicates.
  5. Do not spread in hot loops. Each spread creates a new array. Use push for repeated additions.
Rune AI

Rune AI

Key Insights

  • Spread unpacks arrays into individual elements: use it in array literals ([...arr]) and function calls (fn(...arr)).
  • Copying with spread is shallow: nested objects and arrays share the same reference; use structuredClone() for deep copies.
  • Merging arrays is concise: [...arr1, ...arr2] replaces arr1.concat(arr2) with clearer intent.
  • Spread and rest are opposites: spread expands, rest collects, and context (not syntax) determines which is active.
  • Combine spread with Set for deduplication: [...new Set(arr)] removes duplicates in a single expression.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between spread and rest in JavaScript?

Spread and rest use the same `...` syntax but serve opposite purposes. Spread expands an array or iterable into individual elements (used in array literals and function calls). Rest collects individual elements into an array (used in function parameters and destructuring patterns). The context determines which operation occurs: `[...arr]` spreads, while `function(...args)` rests.

Does the spread operator create a deep copy?

No, spread creates a shallow copy. The top-level elements are copied, but nested arrays and objects remain shared references between the original and the copy. Modifying a nested object in the copy also modifies it in the original. Use `structuredClone()` when you need a completely independent deep copy.

Can I use spread to merge objects?

Yes, but the syntax is slightly different. For objects, use `{ ...obj1, ...obj2 }` (curly braces, not square brackets). Properties from later objects overwrite earlier ones with the same key. This is the standard way to merge configuration objects, default settings, and state updates in modern JavaScript.

Is the spread operator slow?

For most use cases, spread performance is excellent. It becomes a concern only when you spread very large arrays (millions of elements) inside tight loops, because each spread creates a new array copy. For normal application code with arrays under 10,000 elements, the performance difference is negligible compared to `concat()` or manual loops.

How do I spread an array into function arguments?

Write the function call with `...` before the array: `Math.max(...numbers)`. This unpacks the array so each element becomes a separate argument. It works with any function, including your own: `myFunction(...args)` is equivalent to `myFunction(args[0], args[1], args[2], ...)`.

Conclusion

The spread operator is one of the most practical additions in modern JavaScript. It handles array copying, merging, element insertion, function argument passing, and iterable conversion with clean, readable syntax. The key distinction to remember is that spread creates shallow copies, so nested data structures require structuredClone() for full independence. Use spread for everyday immutable array operations, combine it with destructuring for elegant data extraction, and reach for Set spreading to eliminate duplicates efficiently.