Array.prototype.concat()
Non-mutating, unlike push — which is the main reason to still reach for it. Note the asymmetry: an array argument is spread, but only one level deep.
Demo
The elements of the second array are appended to the first, producing a new array — neither input is touched. An empty argument simply contributes nothing. The behaviour the demo cannot show is what happens with a NON-array argument: concat(3) appends the number 3 as one element, while concat([3]) spreads the array and also appends 3. The two look different but give the same result, which is a frequent source of confusion.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| ...values | any | no (none) | Any number of values. Arrays are spread one level; non-arrays are appended whole. With no arguments you get a shallow copy. |
Return value
Array — A NEW array holding this array's elements followed by each argument. Array arguments are flattened one level; everything else is appended as a single element.
Common patterns
const all = a.concat(b); const all2 = [...a, ...b];
const next = items.concat(newItem);
const flat = [].concat(...arrays);
Examples
Pitfalls
[1].concat([2, 3])
[1].concat([[2, 3]])
[1].concat([2, [3]])
[1, [2, [3]]].flat(Infinity)
const a = [{n: 1}]; const b = a.concat(); b[0].n = 99; a[0].n
const b = structuredClone(a);
When to use
- Merging arrays without mutating either
- Appending to state you must not modify in place
- Flattening one level on runtimes older than ES2019
- Modern code merging two arrays → [...a, ...b] reads better
- Appending in place → push, which is faster and clearer about intent
- Deep flattening → flat(Infinity)
Notes
FAQ
They do the same thing for merging arrays. Spread reads better in modern code and handles any iterable, not just arrays. concat is still useful when you want a shallow copy with no arguments, and it is marginally faster on very large arrays in some engines.
const all = [...a, ...b];