spread operator vs array.concat()

JavascriptArraysTypescriptEcmascript 6Operators

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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');

Solution 2 - Javascript

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.

Solution 3 - Javascript

I am replying just to the performance question since there are already good answers regarding the scenarios. I wrote a test and executed it on the most recent browsers. Below the results and the code.

/*
 * Performance results.
 * Browser           Spread syntax      concat method
 * --------------------------------------------------
 * Chrome 75         626.43ms           235.13ms
 * Firefox 68        928.40ms           821.30ms
 * Safari 12         165.44ms           152.04ms
 * Edge 18           1784.72ms          703.41ms
 * Opera 62          590.10ms           213.45ms
 * --------------------------------------------------
*/

Below the code I wrote and used.

const array1 = [];
const array2 = [];
const mergeCount = 50;
let spreadTime = 0;
let concatTime = 0;

// Used to popolate the arrays to merge with 10.000.000 elements.
for (let i = 0; i < 10000000; ++i) {
    array1.push(i);
    array2.push(i);
}

// The spread syntax performance test.
for (let i = 0; i < mergeCount; ++i) {
    const startTime = performance.now();
    const array3 = [ ...array1, ...array2 ];

    spreadTime += performance.now() - startTime;
}

// The concat performance test.
for (let i = 0; i < mergeCount; ++i) {
    const startTime = performance.now();
    const array3 = array1.concat(array2);

    concatTime += performance.now() - startTime;
}

console.log(spreadTime / mergeCount);
console.log(concatTime / mergeCount);

Solution 4 - Javascript

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)

Solution 5 - Javascript

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.

Solution 6 - Javascript

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 ;).

Solution 7 - Javascript

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");

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRamesh RajendranView Question on Stackoverflow
Solution 1 - JavascriptgeorgView Answer on Stackoverflow
Solution 2 - JavascriptBergiView Answer on Stackoverflow
Solution 3 - JavascriptVasile Alexandru PeşteView Answer on Stackoverflow
Solution 4 - JavascriptAnkit AgarwalView Answer on Stackoverflow
Solution 5 - JavascriptMiroslav JonasView Answer on Stackoverflow
Solution 6 - JavascriptPaulJNewellView Answer on Stackoverflow
Solution 7 - JavascriptDrungleView Answer on Stackoverflow