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.
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:
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 3Think 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():
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 Method | Syntax | Readability |
|---|---|---|
| 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:
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 changeMerging 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.
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); // 8Merge with New Elements
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:
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:
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:
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); // 67Real-World Example: Logging with Context
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: 500Converting Iterables to Arrays
Spread works with any iterable, not just arrays:
// 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:
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
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:
| Context | Name | What It Does | Example |
|---|---|---|---|
| In array literals or function calls | Spread | Expands an array into individual elements | [...arr], fn(...args) |
| In function parameters or destructuring | Rest | Collects individual elements into an array | function(...args), const [a, ...rest] = arr |
// 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)); // 10Common Mistakes
Spreading Non-Iterables
// 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
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
// 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
- Use spread for immutable updates. When you need a new array without mutating the original,
[...arr, newItem]is cleaner than.concat(). - Prefer spread over
apply.Math.max(...arr)reads better thanMath.max.apply(null, arr). - Use
structuredClonefor nested data. Spread only creates shallow copies; nested references are shared. - Combine with Set for unique values.
[...new Set(arr)]is the most concise way to remove duplicates. - Do not spread in hot loops. Each spread creates a new array. Use push for repeated additions.
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]replacesarr1.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.
Frequently Asked Questions
What is the difference between spread and rest in JavaScript?
Does the spread operator create a deep copy?
Can I use spread to merge objects?
Is the spread operator slow?
How do I spread an array into function arguments?
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.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.