What Is the Spread Operator? The Three Dots AI Puts Everywhere
If you've read any AI-generated JavaScript, you've seen ... everywhere — in arrays, objects, function calls, React state updates. Those three dots do something specific and powerful. Here's what.
TL;DR
The spread operator (...) unpacks arrays and objects. [...arr1, ...arr2] merges arrays. {...obj, name: 'new'} copies an object with overrides. It creates shallow copies — the original stays unchanged. AI uses it constantly in React for immutable state updates: setState({...state, count: state.count + 1}). The same ... syntax in function parameters is called "rest" and does the opposite — collects arguments.
Why AI Coders See ... Everywhere
The spread operator is one of AI's most-used JavaScript features because it solves the #1 pattern in modern web development: creating a new copy of data with some changes, without modifying the original.
This matters because:
- React requires it: State updates must create new objects, not modify existing ones
- Bugs from mutation: Changing an object directly can cause side effects in other parts of your code that reference it
- API response transformation: Adding or removing fields from API data before using it
Real Scenario
"I have a user object from the API. I need to add a 'role' field set to 'admin' and change the 'status' to 'active', without modifying the original user object."
// AI generates:
const originalUser = { id: 1, name: 'Chuck', status: 'pending', email: 'chuck@example.com' }
// Spread: copy all properties, then override/add specific ones
const updatedUser = {
...originalUser, // Copy everything from originalUser
status: 'active', // Override status (was 'pending', now 'active')
role: 'admin' // Add new property
}
// updatedUser: { id: 1, name: 'Chuck', status: 'active', email: 'chuck@example.com', role: 'admin' }
// originalUser: UNCHANGED — still has status: 'pending', no 'role' field
console.log(originalUser.status) // 'pending' — original untouched
console.log(updatedUser.status) // 'active' — new object has the update
The Three Uses
1. Spread with Objects (Most Common)
// Copy an object
const copy = { ...original }
// Copy and update
const updated = { ...user, name: 'New Name' }
// Merge two objects (second wins on conflicts)
const merged = { ...defaults, ...userPreferences }
// React state update
const [user, setUser] = useState({ name: '', email: '' })
setUser({ ...user, name: 'Chuck' }) // Only changes name, keeps email
// Order matters! Last one wins:
const result = { ...defaults, ...overrides }
// defaults: { theme: 'light', lang: 'en' }
// overrides: { theme: 'dark' }
// result: { theme: 'dark', lang: 'en' } ← overrides won for 'theme'
2. Spread with Arrays
// Copy an array
const copy = [...originalArray]
// Merge arrays
const allItems = [...array1, ...array2]
// Add an item to the beginning or end
const withNewFirst = [newItem, ...existingItems]
const withNewLast = [...existingItems, newItem]
// Remove an item (combine with filter)
const without3 = [...items.filter(item => item.id !== 3)]
// Convert something iterable to an array
const chars = [...'hello'] // ['h', 'e', 'l', 'l', 'o']
const nodeArray = [...document.querySelectorAll('.item')]
3. Spread in Function Calls
// Pass array elements as individual arguments
const numbers = [5, 10, 15, 20]
Math.max(...numbers) // Same as Math.max(5, 10, 15, 20) → 20
console.log(...numbers) // Same as console.log(5, 10, 15, 20)
// Useful with functions that expect individual arguments, not arrays
Spread vs. Rest: Same Dots, Opposite Jobs
// SPREAD: expands an array/object INTO individual elements
const merged = [...arr1, ...arr2] // Expands arrays into new array
const copy = { ...obj } // Expands object into new object
// REST: collects individual elements INTO an array/object
function sum(...numbers) { // Collects all args into 'numbers' array
return numbers.reduce((a, b) => a + b, 0)
}
sum(1, 2, 3, 4) // numbers = [1, 2, 3, 4]
// REST with destructuring: collect "the rest"
const { id, ...otherFields } = user
// id = user.id
// otherFields = everything else (name, email, etc.)
const [first, ...remaining] = [1, 2, 3, 4, 5]
// first = 1
// remaining = [2, 3, 4, 5]
Quick rule: On the left side of = or in function parameters → rest (collecting). On the right side of = or in expressions → spread (expanding).
What AI Gets Wrong
1. Shallow Copy Trap
const original = {
name: 'Chuck',
address: { city: 'Coeur d\'Alene', state: 'ID' } // Nested object
}
const copy = { ...original }
copy.address.city = 'Boise' // Changing the COPY
console.log(original.address.city) // 'Boise' ← ORIGINAL CHANGED TOO!
// Spread only copies the top level. Nested objects are still shared references.
// Fix: deep copy nested objects explicitly
const safeCopy = {
...original,
address: { ...original.address } // Spread the nested object too
}
// Or use: structuredClone(original) for true deep copy
2. Wrong Property Order
// AI sometimes writes this backwards:
const config = { ...userSettings, ...defaults }
// This lets defaults OVERRIDE user settings — probably wrong!
// Correct: defaults first, user overrides second
const config = { ...defaults, ...userSettings }
// User's choices override defaults — correct
3. Spreading in Loops (Performance)
// BAD: Creates a new array on every iteration — O(n²) time
let result = []
for (const item of items) {
result = [...result, transform(item)] // Copies entire array each time!
}
// GOOD: Just push
const result = []
for (const item of items) {
result.push(transform(item))
}
// Or better: just use map
const result = items.map(transform)
What to Learn Next
Frequently Asked Questions
What does the spread operator (...) do?
The spread operator unpacks arrays or objects. With arrays: [...arr1, ...arr2] merges them. With objects: {...obj, key: 'value'} copies and overrides. It creates shallow copies — originals are never modified. AI uses it constantly for immutable updates in React and data transformation.
What is the difference between spread and rest?
Same ... syntax, opposite purpose. Spread expands: [...arr1, ...arr2] unpacks arrays. Rest collects: function sum(...nums) gathers arguments into an array. In function parameters → rest. In expressions and assignments → spread.
Does spread create a deep copy?
No — spread creates a shallow copy. Top-level properties are independent, but nested objects are still shared references. Modifying a nested property in the copy changes the original. For deep copies: structuredClone(obj) (Node 17+/modern browsers) or spread nested objects explicitly: {...obj, nested: {...obj.nested}}.
Why does AI use spread in React state updates?
React requires immutable state updates — you must create new objects/arrays so React detects the change and re-renders. setState({...state, count: state.count + 1}) creates a new object with all existing properties plus the updated count. Direct mutation (state.count++) doesn't trigger re-renders and causes bugs.
What does AI get wrong about spread?
AI sometimes relies on spread for deep copies (it only copies one level deep). It puts properties in the wrong order ({...overrides, ...defaults} should be reversed). And it sometimes uses spread inside loops, creating a new array copy on every iteration — an O(n²) performance trap that should use push or map instead.