String concat in JavaScript Performance Comparison

eye-catch JavaScript/TypeScript

JavaScript offers several ways to concatenate strings. Let’s learn how to concatenate strings and which one is the best performance.

Sponsored links

Plus Operator and Template Strings/Literal (backtick/backquote)

Using the plus operator to concatenate strings is supported by many languages. We can put variables into a statement without plus operator by using “`”. It is called Template Literal which was called Template String before.

const str1 = "Hello";
const str2 = "I'm";
const str3 = "Yuto";

const msg = str1 + "," + str2 + " " + str3;
const msg2 = `${str1}, ${str2} ${str3}`;
const msg3 = str1.concat(str2, str3);
console.log(msg);
// Hello,I'm Yuto
console.log(msg2);
// Hello, I'm Yuto
Sponsored links

Template Literal is not readable

Template Literal is useful because we can put everything in the same statement. However, it gets a bit unreadable when the variable names are long.

const longKeywordVariable = "key";
const longKeywordNumber = 12
const longKeywordValue = "value";
const msg = `${longKeywordVariable}-${longKeywordNumber}-${longKeywordValue}`;
console.log(msg);
// key-12-value

We may need to scroll to see what values are used. The first improvement is the following.

const msg2 = `${longKeywordVariable}-
${longKeywordNumber}-
${longKeywordValue}`;

It’s more readable than before but it doesn’t work as expected. The result is the following.

// key-
// 12-
// value

Since it is the same statement and each variable is written on a new line. It needs to be separated as follows.

const msg3 = `${longKeywordVariable}-`
    + `${longKeywordNumber}-`
    + `${longKeywordValue}`;
console.log(msg3);
// key-12-value

Furthermore, using Array.prototype.join function is better when the variables are concatenated with the same string.

const msg4 = [
    longKeywordVariable,
    longKeywordNumber,
    longKeywordValue,
].join("-");
console.log(msg4);
// key-12-value

Performance comparison concat vs Template Literal vs Array.join

I measured the performance difference. The common function is the following.

import { performance } from "perf_hooks";

function doLoop(cb: () => void) {
    for (let i = 0; i < 1000000; i++) {
        cb();
    }
}

type TestDataType = { startStr: string, copiedStr: string };
const copiedStr = "x".repeat(100);

function measure(title: string, cb: (args: TestDataType) => void) {
    const start = performance.now();
    const args = { startStr: "", copiedStr };
    cb(args);
    const result = performance.now() - start;
    console.log(`result(${title}): ${Math.round(result)} ms`);
}

What we need to do is to define the callback function and set it to cb. I defined the following functions.

const runConcat = () => measure("concat with", (args: TestDataType) => {
    const cb = () => {
        args.startStr = args.startStr.concat(args.copiedStr);
    }
    doLoop(cb);
});
const runPlus = () => measure("+ operator", (args: TestDataType) => {
    const cb = () => { args.startStr += args.copiedStr };
    doLoop(cb);
});
const runLiteral = () => measure("Template Literal", (args: TestDataType) => {
    const cb = () => {
        args.startStr = `${args.startStr}${args.copiedStr}`;
    };
    doLoop(cb);
});
const runJoin = () => measure("Array.join with loop", () => {
    const result: string[] = [];
    doLoop(() => {
        result.push(copiedStr);
    });
    result.join("");
});

const runReduce = () => measure("Array.reduce with loop", () => {
    const result: string[] = [];
    doLoop(() => {
        result.push(copiedStr);
    });
    result.reduce((pre, cur) => pre + cur);
});

// ------ without loop -------

const array: string[] = [];
doLoop(() => {
    array.push(copiedStr);
});

const runJoinWithout = () => measure("Array.join without loop", () => array.join(""));

const runReduceWithout = () => measure("Array.reduce without loop", () => {
    array.reduce((pre, cur) => pre + cur);
});

for (let i = 0; i < 10; i++) {
    runConcat();
    // runLiteral();
    // runPlus();
    // runJoin();
    // runJoinWithout();
    // runReduce();
    // runReduceWithout();
}

The last for loop is the statement for the main process. I executed only one function at a time so that I could measure without side effects. This is the result including for-loop.

N/AconcatjoinTemplate Literalreduce+ operator
892268913588
245238247183253
163234188156170
162259168156156
154376166166172
165324168162155
161258176156163
159235167164152
17622817419198
153253182176172
Ave162.7263.1172.5164.5157.9

This result doesn’t include for-loop which means using the array directly.

N/Ajoin withoutreduce without
19788
15494
193176
15995
17189
15694
17497
17590
16989
22096
Ave176.8100.8

The compiler couldn’t build the following code because the number of arrays is too big to put the argument.

const runConcatWithout = () => {
    measure("concat without loop", (args: TestDataType) => {
        args.startStr.concat(...array);
    });
}
// RangeError: Maximum call stack size exceeded

By the way, the versions when I measured this is the following.

$ tsc -v
Version 4.0.3

$ node -v
v14.13.0

Summary

Array.prototype.join function is the slowest to concatenate string. Others are almost the same performance. In most cases, we don’t have to take care of which one to use. Its difference is very small even though the string is long.

Comments

  1. Alex Vincent says:

    The readability concern with long variable names is unwarranted. The following is perfectly legal, thanks to JavaScript’s handling of white-space.


    const msg2 = `${
    longKeywordVariable
    }-${
    longKeywordNumber
    }-${
    longKeywordValue
    }`;

Copied title and URL