162

What is the difference between spread operator and array.concat()

let parts = ['four', 'five'];
let numbers = ['one', 'two', 'three'];
console.log([...numbers, ...parts]);

Array.concat() function

let parts = ['four', 'five'];
let numbers = ['one', 'two', 'three'];
console.log(numbers.concat(parts));

Both results are same. So, what kind of scenarios we want to use them? And which one is best for performance?

11

8 Answers 8

267

concat and spreads are very different when the argument is not an array.

When the argument is not an array, concat adds it as a whole, while ... tries to iterate it and fails if it can't. Consider:

a = [1, 2, 3]
x = 'hello';

console.log(a.concat(x));  // [ 1, 2, 3, 'hello' ]
console.log([...a, ...x]); // [ 1, 2, 3, 'h', 'e', 'l', 'l', 'o' ]

Here, concat treats the string atomically, while ... uses its default iterator, char-by-char.

Another example:

x = 99;

console.log(a.concat(x));   // [1, 2, 3, 99]
console.log([...a, ...x]);  // TypeError: x is not iterable

Again, for concat the number is an atom, ... tries to iterate it and fails.

Finally:

function* gen() { yield *'abc' }

console.log(a.concat(gen()));   // [ 1, 2, 3, Object [Generator] {} ]
console.log([...a, ...gen()]);  // [ 1, 2, 3, 'a', 'b', 'c' ]

concat makes no attempt to iterate the generator and appends it as a whole, while ... nicely fetches all values from it.

To sum it up, when your arguments are possibly non-arrays, the choice between concat and ... depends on whether you want them to be iterated.

The above describes the default behaviour of concat, however, ES6 provides a way to override it with Symbol.isConcatSpreadable. By default, this symbol is true for arrays, and false for everything else. Setting it to true tells concat to iterate the argument, just like ... does:

str = 'hello'
console.log([1,2,3].concat(str)) // [1,2,3, 'hello']

str = new String('hello');
str[Symbol.isConcatSpreadable] = true;
console.log([1,2,3].concat(str)) // [ 1, 2, 3, 'h', 'e', 'l', 'l', 'o' ]

Performance-wise concat is faster, probably because it can benefit from array-specific optimizations, while ... has to conform to the common iteration protocol. Timings:

let big = (new Array(1e5)).fill(99);
let i, x;

console.time('concat-big');
for(i = 0; i < 1e2; i++) x = [].concat(big)
console.timeEnd('concat-big');

console.time('spread-big');
for(i = 0; i < 1e2; i++) x = [...big]
console.timeEnd('spread-big');


let a = (new Array(1e3)).fill(99);
let b = (new Array(1e3)).fill(99);
let c = (new Array(1e3)).fill(99);
let d = (new Array(1e3)).fill(99);

console.time('concat-many');
for(i = 0; i < 1e2; i++) x = [1,2,3].concat(a, b, c, d)
console.timeEnd('concat-many');

console.time('spread-many');
for(i = 0; i < 1e2; i++) x = [1,2,3, ...a, ...b, ...c, ...d]
console.timeEnd('spread-many');

Sign up to request clarification or add additional context in comments.

3 Comments

This should be the correct answer. Less subjective than bergi's answer.
for some reason, array.concat was not working for me in a typescript project. So I had to use array.push(...spread). I found this link which talks about the concat issue ion typescript which suggest to use ([] as any[]).concat(myArray), but that doesn't seem to work either.
@Gangula array.concat should work fine with typescript tutorialspoint.com/typescript/typescript_array_concat.htm
90

.concat() is faster, otherwise they work the same assuming both arguments are lists. Here's the time it takes to merge two arrays with 10 million elements each (lower is better):

Browser [...a, ...b] a.concat(b)
Chrome 113 350ms 30ms
Firefox 113 400ms 63ms
Safari 16.4 92ms 71ms

I ran this code on an M1 MacBook Air with 8GB of RAM:

const arraySize = 10000000;
const trials = 50;

const array1 = [];
const array2 = [];
for (let i = 0; i < arraySize; ++i) {
  array1.push(i);
  array2.push(i);
}

let spreadTime = 0;
for (let i = 0; i < trials; ++i) {
  const start = performance.now();
  const array3 = [...array1, ...array2];
  const end = performance.now();

  spreadTime += end - start;
}

let concatTime = 0;
for (let i = 0; i < trials; ++i) {
  const start = performance.now();
  const array3 = array1.concat(array2);
  const end = performance.now();

  concatTime += end - start;
}

// performance.now() returns milliseconds with a
// 5 microsecond resolution in isolated contexts and a
// 100 microsecond resolution in non-isolated contexts.
spreadTime = Math.round(spreadTime / trials * 1000) / 1000;
concatTime = Math.round(concatTime / trials * 1000) / 1000;
console.log(`${arraySize} items - spread: ${spreadTime}ms concat: ${concatTime}ms`);

3 Comments

Thanks, this is actually a useful answer in terms of what actually matters.
I rarely have 10.000.000 elements. Would rather/also like to see a comparison of merging 10, 100 or 1000 elements, and doing the merge many times.
@daniero there's this post that does exactly that - jonlinnell.co.uk/articles/spread-operator-performance
80

Well console.log(['one', 'two', 'three', 'four', 'five']) has the same result as well, so why use either here? :P

In general you would use concat when you have two (or more) arrays from arbitrary sources, and you would use the spread syntax in the array literal if the additional elements that are always part of the array are known before. So if you would have an array literal with concat in your code, just go for spread syntax, and just use concat otherwise:

[...a, ...b] // bad :-(
a.concat(b) // good :-)

[x, y].concat(a) // bad :-(
[x, y, ...a]    // good :-)

Also the two alternatives behave quite differently when dealing with non-array values.

14 Comments

Is it possible to do concat or spread an array in between an another array?
@RameshRajendran In between two other arrays, sure. "In between an other array", not sure what you mean.
@RameshRajendran The equivalent to that would be ['one'].concat(parts, ['two', 'three']) (or ['one'].concat(parts).concat(['two', 'three']) if you don't want to pass multiple arguments)
FWIW, there's a measurable performance difference. See jsperf.com/spread-vs-concat-vs-push
@DrazenBjelovuk .concat(x) makes the reader assume that x is an array as well. Sure, concat can handle non-array values as well, but imo that's not its main mode of operation. Especially if x is an arbitrary (unknown) value, you would need to write .concat([x]) to make sure it always works as intended. And as soon as you have to write an array literal anyway, I say that you should just use spread syntax instead of concat.
|
16

The one difference I think is valid is that using spread operator for large array size will give you error of Maximum call stack size exceeded which you can avoid using the concat operator.

var  someArray = new Array(600000);
var newArray = [];
var tempArray = [];


someArray.fill("foo");

try {
  newArray.push(...someArray);
} catch (e) {
  console.log("Using spread operator:", e.message)
}

tempArray = newArray.concat(someArray);
console.log("Using concat function:", tempArray.length)

4 Comments

This use of spread syntax (function call) is not what was asked about (array literal).
I know that, the OP has not push the element. But we usually push the element in array so I am trying to show the consequences when we will use the push with spread.
You should clarify that the stack gets used when a function call uses spread inside. However when it is an array literal only and the spread is used, no stack is ever used so no max call stack will happen.
This is not relevant to the question and its kind of misleading
10

Update:

Concat is now always faster than spread. The following benchmark shows both small and large-size arrays being joined: https://jsbench.me/nyla6xchf4/1

enter image description here

// preparation
const a = Array.from({length: 1000}).map((_, i)=>`${i}`);
const b = Array.from({length: 2000}).map((_, i)=>`${i}`);
const aSmall = ['a', 'b', 'c', 'd'];
const bSmall = ['e', 'f', 'g', 'h', 'i'];

const c = [...a, ...b];
// vs
const c = a.concat(b);

const c = [...aSmall, ...bSmall];
// vs
const c = aSmall.concat(bSmall)

Previous:

Although some of the replies are correct when it comes to performance on big arrays, the performance is quite different when you are dealing with small arrays.

You can check the results for yourself at https://jsperf.com/spread-vs-concat-size-agnostic.

As you can see, spread is 50% faster for smaller arrays, while concat is multiple times faster on large arrays.

4 Comments

Link is broken—and for this reason, it's always best to provide a summary of the linked content in case the link ever breaks.
not sure if this is correct. see stackoverflow.com/a/74161832/3370568
It's not fully correct anymore due to changes in the browser, but the linked answer is also not correct. Both tests include preparation - most of the test's duration is spent on array generation instead of testing the values. Here is the correct version jsbench.me/nyla6xchf4/1
I still get faster performance from spread on smaller arrays today, seems nothing has changed there
8

There is one very important difference between concat and push in that the former does not mutate the underlying array, requiring you to assign the result to the same or different array:

let things = ['a', 'b', 'c'];
let moreThings = ['d', 'e'];
things.concat(moreThings);
console.log(things); // [ 'a', 'b', 'c' ]
things.push(...moreThings);
console.log(things); // [ 'a', 'b', 'c', 'd', 'e' ]

I've seen bugs caused by the assumption that concat changes the array (talking for a friend ;).

Comments

2

The answer by @georg was helpful to see the comparison. I was also curious about how .flat() would compare in the running and it was by far the worst. Don't use .flat() if speed is a priority. (Something I wasn't aware of until now)

  let big = new Array(1e5).fill(99);
  let i, x;

  console.time("concat-big");
  for (i = 0; i < 1e2; i++) x = [].concat(big);
  console.timeEnd("concat-big");

  console.time("spread-big");
  for (i = 0; i < 1e2; i++) x = [...big];
  console.timeEnd("spread-big");

  console.time("flat-big");
  for (i = 0; i < 1e2; i++) x = [[], big].flat();
  console.timeEnd("flat-big");

  let a = new Array(1e3).fill(99);
  let b = new Array(1e3).fill(99);
  let c = new Array(1e3).fill(99);
  let d = new Array(1e3).fill(99);

  console.time("concat-many");
  for (i = 0; i < 1e2; i++) x = [1, 2, 3].concat(a, b, c, d);
  console.timeEnd("concat-many");

  console.time("spread-many");
  for (i = 0; i < 1e2; i++) x = [1, 2, 3, ...a, ...b, ...c, ...d];
  console.timeEnd("spread-many");

  console.time("flat-many");
  for (i = 0; i < 1e2; i++) x = [1, 2, 3, a, b, c, d].flat();
  console.timeEnd("flat-many");

Comments

1

I took georg's test and compare perfomance result between setting in empty array and a previous array

Here the result, I thought it can interest some poeple Tl;dr : using a specific array for the result is way faster

let big = (new Array(1e5)).fill(99);
let big2 = (new Array(1e5)).fill(99);
let res = []

console.time('concat-many');
for (i = 0; i < 1e2; i++) res = big.concat(big2)
console.timeEnd('concat-many');

console.time('concat-many-with-reset');
for (i = 0; i < 1e2; i++) big = big.concat(big2)
console.timeEnd('concat-many-with-reset');

res = []
big = (new Array(1e5)).fill(99);
console.time('spread-many');
for (i = 0; i < 1e2; i++) res = [...big, ...big2]
console.timeEnd('spread-many');


console.time('spread-many-with-reset');
for (i = 0; i < 1e2; i++) big = [...big, ...big2]
console.timeEnd('spread-many-with-reset');

1 Comment

these are not comparing like things. It just shows creating smaller arrays is faster than creating bigger ones

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.