spread operator vs array.concat()

57,841

Solution 1

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 2

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 3

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

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

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.

Share:
57,841

Related videos on Youtube

Ramesh Rajendran
Author by

Ramesh Rajendran

Technologies : HTML CSS Asp.Net MVC Web Api C# Angular 1-7 Unit Test (Front End &amp; Back End) I am currently working in L&amp;T . Read my blog C# DotNet Solutions.

Updated on April 12, 2022

Comments

  • Ramesh Rajendran
    Ramesh Rajendran about 2 years

    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?

    • Ele
      Ele about 6 years
      @gurvinder372 I think the OP knows that.
    • Fabien Greard
      Fabien Greard about 6 years
    • Jota.Toledo
      Jota.Toledo about 6 years
      The example isnt the best as already pointed. Please update
    • Ramesh Rajendran
      Ramesh Rajendran about 6 years
      @FabienGreard That is nice information.
    • Ele
      Ele about 6 years
      Primarily opinion-based question!
    • nicowernli
      nicowernli about 6 years
    • Ramesh Rajendran
      Ramesh Rajendran about 6 years
      @nicowernli Am asking about concat() not push().
    • Suraj Rao
      Suraj Rao about 6 years
    • Ramesh Rajendran
      Ramesh Rajendran almost 5 years
      @SurajRao There is no much more information than this below answer
    • Suraj Rao
      Suraj Rao almost 5 years
      @RameshRajendran true.. added it as it is related
  • Bergi
    Bergi about 6 years
    This use of spread syntax (function call) is not what was asked about (array literal).
  • Ramesh Rajendran
    Ramesh Rajendran about 6 years
    Is it possible to do concat or spread an array in between an another array?
  • Ankit Agarwal
    Ankit Agarwal about 6 years
    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.
  • Bergi
    Bergi about 6 years
    @RameshRajendran In between two other arrays, sure. "In between an other array", not sure what you mean.
  • Ramesh Rajendran
    Ramesh Rajendran about 6 years
    For example I can use spread operators in between the array objects. ['one',...parts, 'two', 'three'];. now four and five is move to second potion. Is it possible in concat()
  • Bergi
    Bergi about 6 years
    @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)
  • Luis Villavicencio
    Luis Villavicencio over 5 years
    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.
  • broofa
    broofa over 5 years
    FWIW, there's a measurable performance difference. See jsperf.com/spread-vs-concat-vs-push
  • Drazen Bjelovuk
    Drazen Bjelovuk over 4 years
    Your second example could have also been written a.concat(x).concat(y), which is imo just as silly, so I'd personally include requiring multiple concats as additional qualifier for your spread criteria.
  • Bergi
    Bergi over 4 years
    @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.
  • Bergi
    Bergi over 4 years
    @DrazenBjelovuk Of course there is: when x is an array (or some other concatSpreadable object), the one expression would spread it and the other would append x as a single element (the desired result).
  • Drazen Bjelovuk
    Drazen Bjelovuk over 4 years
    @Bergi Ah, I see what you're getting at. a.concat(x).concat(y) would not necessarily be equivalent given that either x or y were arrays themselves. Very good point. Though by that logic, if a were unknown and your intention was to simply tack it on in one piece in the case that it wasn't an array, your spread alternative would break down.
  • Bergi
    Bergi over 4 years
    @DrazenBjelovuk In the example, a and b are supposed to be arrays of (arbitrary) elements, and x and y are supposed to be (arbitrary) element values.
  • Drazen Bjelovuk
    Drazen Bjelovuk over 4 years
    @Bergi I'm just saying in your second example, if a could be either an array or non-array value (unknown) and your intention were to tack it on as a single element, your criteria of "just use spread if your statement includes an array literal" wouldn't fit the bill. It's non-categorical.
  • Bergi
    Bergi over 4 years
    @DrazenBjelovuk Same in the first example: if a wasn't an array, then a.concat(…) didn't make any sense. Yes, to tack on a single element, I would never pass it to concat directly but always wrap it in an array literal - and that's where spread syntax comes in.
  • Drazen Bjelovuk
    Drazen Bjelovuk over 4 years
    @Bergi Sorry, I think I was unclear in describing the scenario. To clarify: a is either an array or non-array (unknown), if it's an array, I want to spread it, if it's not, I want to tack it on. [x, y].concat(a) satisfies this, [x, y, ...a] does not. Following your rule of thumb here would lead to undesired behaviour.
  • Bergi
    Bergi over 4 years
    @DrazenBjelovuk Well that's why it's only a rule of thumb, not a law set in stone: if you have weird requirements, you're an exception to the rule :-) Also see the last sentence of my answer as a clear disclaimer.
  • lintuxvi
    lintuxvi almost 4 years
    This should be the correct answer. Less subjective than bergi's answer.
  • King Friday
    King Friday almost 4 years
    Thanks, this is actually a useful answer in terms of what actually matters.
  • daniero
    daniero over 3 years
    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.
  • bumbeishvili
    bumbeishvili over 3 years
    This is not relevant to the question and its kind of misleading
  • David I. Samudio
    David I. Samudio about 3 years
    This is the right answer, thank you, Paul.
  • Scott Schupbach
    Scott Schupbach almost 3 years
    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.