Which is more efficient str += "some string" or array.push().join()

Which is more efficient str += "some string" or array.push().join()
0

#1

I know String in JavaScript is immutable which means if I concatenate strings by using +=, a new string will be created with its content being copied from the old string, the old string then gets garbage collected, but if I want to construct a string from smaller strings. which of the following 2 approaches is better.
1st approach:

var str = “”;
for (var i = 0; i < 10; i++) {
str += i;
}
return str;

2nd approach:

var array = [];
for (var i = 0; i < 10; i++) {
array.push(i);
}
return array.join("");


#2

@nhuyvan1106 Good question. In principal, your assumption is correct. In many programming languages, using string concatenation in loops is discouraged and there are dedicated classes to do that (i.e. StringBuilder in C#/Java).

Coming from those languages, I thought I should avoid += in loops, too. However, I just looked it up to be sure and here is what I’ve gathered: Modern JavaScript engines will internally optimize your first approach to behave like the second, so there is no need to do that yourself. [1]

I think one should try to write code that is easy to read for humans, even at the cost of small performance penalties.
If in doubt, you can always do a speed test yourself:

function test() {
    Math.sqrt(2);
}
var iterations = 1000000;
    time = 0, start, stop;
for (var i = 0; i < iterations; i++) {
    start = Date.now();
    test();
    stop = Date.now();
    time += stop - start;
}
console.log('Average time taken: ' + (time / iterations) +
            'ms' +  '-- total:' + time + 'ms');

Do this for both algorithms and compare the results. If you’re lazy, like me, you can also ask google instead :slight_smile:


#3

This. 100 times this. Nowadays we aren’t scraping for every byte and millisecond so there is no reason not to write code that couldn’t be easily parsed by some stranger coming in after you.


#4

I’ve made a simple test:

'use strict';
let count = 10000;
let source = 'abcdefghijklmnopqrstuvwxyz';

function concatAsString() {
    var str = '';
    for (var c = 0; c < count; c++) {
        for (var i = 0; i < source.length; i++) {
            str += source[i];
        }
    }
    return str;
}

function concatAsArray() {
    var array = [];
    for (var c = 0; c < count; c++) {
        for (var i = 0; i < source.length; i++) {
            array.push(source[i]);
        }
    }
    return array.join("");
}

function concatAsTrueString() {
    var str = new String();
    for (var c = 0; c < count; c++) {
        for (var i = 0; i < source.length; i++) {
            str += source[i];
        }
    }
    return str;
}

// ----------- TEST -----------------
function runTest() {
    console.log(`--------------------------`);
    console.log(`"concatAsString" started..`);
    var t0 = performance.now();
    var str1 = concatAsString();
    var t1 = performance.now();
    console.log(`"concatAsString" completed:`);
    console.log("Time 1: " + (t1 - t0) + " milliseconds.");

    console.log(`--------------------------`);
    console.log(`"concatAsArray" started..`);
    t0 = performance.now();
    var str2 = concatAsArray();
    t1 = performance.now();
    console.log(`"concatAsArray" completed:`);
    console.log("Time 2: " + (t1 - t0) + " milliseconds.");

    console.log(`--------------------------`);
    console.log(`"concatAsTrueString" started..`);
    t0 = performance.now();
    var str3 = concatAsTrueString();
    t1 = performance.now();
    console.log(`"concatAsTrueString" completed:`);
    console.log("Time 3: " + (t1 - t0) + " milliseconds.");
}

runTest();

It should be run several times until the results are stabilized in Chrome. Here they are:


“concatAsString” started…
“concatAsString” completed:
Time 1: 7.699999958276749 milliseconds.


“concatAsArray” started…
“concatAsArray” completed:
Time 2: 29.69999995548278 milliseconds.


“concatAsTrueString” started…
“concatAsTrueString” completed:
Time 3: 94.40000005997717 milliseconds.

Interestingly enough, strange, and unexpected to me that the String class is much slower than its primitive counterpart. It’s even slower than the Array. I’m very new to JS world and I guess I just don’t know something about it.

If FireFox the results are not too different from each other:


“concatAsString” started…
“concatAsString” completed:
Time 1: 103 milliseconds.


“concatAsArray” started…
“concatAsArray” completed:
Time 2: 101 milliseconds.


“concatAsTrueString” started…
“concatAsTrueString” completed:
Time 3: 111 milliseconds.


#5

Takeaway is don’t ever use constructors for primitive values: if you just concatenate primitive strings, JS engines can short circuit the process of concatenation, they don’t necessarily have to bother creating a wrapper object. If you explicitly create a wrapper object, then you’re forcing the engine to use the slow path.

Also, more generally, @nhuyvan1106, + has always and will always be faster (unless the engine can acertain that the array.join… operation is just concatenating strings and replace it with +, which doesn’t seem feasible) - with join you’re creating an intermediate object, so there’s another level of indirection.


#6

Thank you, Dan!
I also found some things explained on stackoverflow’s thread.