A literal is a textual representation (notation) of a value as it is written in source code. Many people generally associate the concept of literals with performance “cheapness”: A literal always seems to consume very little resource. Is this true? This post discusses the hidden performance cost of literals in JavaScript.

Distinction Between Object and Primitive Literals

Literals can be divided into two categories: Literals that represent objects and literals that represent primitives.

An Example of an Object Literal

Consider the following pair of code snippets:

const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let i = 0; i < 10000; ++i) {
  a.sort();
}
for (let i = 0; i < 10000; ++i) {
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].sort();
}

The first code snippet is expected to run faster, because the second code snippet constructs the array 10000 times, while the first code snippet only constructs it once. The first code snippet looks up the value of a 10000 times, which is faster than constructing the array 10000 times. Our experiments with jsbench.me and Chrome 126 showed that the second code snippet was about 20.78% slower.

An Example of a Primitive Literal

Now we modify the code snippets above slightly to the following pair of code snippets:

const b = 1;
for (let i = 0; i < 10000; ++i) {
  b + 3;
}
for (let i = 0; i < 10000; ++i) {
  1 + 3;
}

They are expected to take similar time for a JavaScript engine to execute. Although the second code snippet constructs 1 10000 times, creating 1 is still faster than looking up the value of b in the first code snippet. Our experiments with jsbench.me and Chrome 126 showed that they were similarly fast with less than 1% difference in their running time.

Comparison

Although in both pairs of code snippets [1, ...] and 1 are literals, the former is an object while the latter is a primitive. Generally speaking, constructing an object is relatively more expensive than looking up the value of a variable. Therefore, the second pair of code snippets has performance difference while the first pair does not. We now conclude: An object literal is not cheap in terms of performance. If a line with an object literal is expected to be executed multiple times, Performance-wise it is usually a good idea to consider assigning it to a variable beforehand and using this variable instead.

Common Performance Trap: No-op Functions

One literal regarding which people tend to forget this point is no-op functions, such as () => {}. When a placeholder callback is needed, people often throw in () => {} without much thought. However, a JavaScript engine always creates a new object () => {} in the process. For example, our experiments with jsbench.me and Chrome 127 showed that the second code snippet below was about 85.06% slower than the first code snippet.

const noop = () => {};
for (let i = 0; i < 10000; ++i) {
  noop;
}
for (let i = 0; i < 10000; ++i) {
  () => {};
}

For better performance, consider a global no-op function in your program, or one from a utility library that you are already using, such as lodash’s noop function.

A Tricky Object Literal: Regular Expressions

Consider the following two code snippets. Which one is faster? Or, are they similarly fast?

const b = /a*b/;
for (let i = 0; i < 10000; ++i) {
  b.exec("aab");
}
for (let i = 0; i < 10000; ++i) {
  /a*b/.exec("aab");
}

The answer is: The first code snippet is faster. Our experiments with jsbench.me and Chrome 126 showed that the second code snippet was about 7.19% slower.

If you find this answer counterintuitive, chances are you have conflated the timing of regular expression compilation versus regular expression object creation.

Two Ways to Create a Regular Expression

JavaScript permits creating regular expressions using literals. For example,

const re = /a*b/;

Alternatively, one can write:

const re = new RegExp("a*b");

According to MDN, /a*b/ is more efficient than new RegExp("a*b") because:

Regular expression literals provide compilation of the regular expression when the script is loaded… Using the constructor function provides runtime compilation of the regular expression.

In other words, an optimized JavaScript engine compiles the regular expression with /a*b/ when loading the script while it only compiles the regular expression with new RegExp("a*b") when executing that line of code. No text mentions the timing of creating a regular expression (RegExp) object—Compiling the regular expression is only one intermediate step in creating a regular expression object.

The Real Performance Cost of a Regular Expression Literal

The ECMA Standard says:

A regular expression literal is an input element that is converted to a RegExp object each time the literal is evaluated.

In other words, when a JavaScript engine encounters a regular expression literal during runtime, it constructs a RegExp object. This is not so different from other objects such as the [1, ...] array in the previous section, except that the JavaScript engine has done some work ahead of time, i.e., compiling the regular expression.

Now let’s revisit the two code snippets at the beginning of the section. The first code snippet is faster at the beginning of this section because it only constructs the RegExp object once, while the second code snippet constructs the RegExp object 10000 times.

Non-Fundamental Primitive Types

Which of the following code snippets is faster? Or, are they similarly fast?

const s = "aaaaaaaaaaaaaaaaaaaa";
for (let i = 0; i < 10000; ++i) {
  s + "b";
}
for (let i = 0; i < 10000; ++i) {
  "aaaaaaaaaaaaaaaaaaaa" + "b";
}

Our experiments with Chrome 126 and Firefox 128 on jsbench.me showed that they were similarly fast with less than 1% difference in their running time.

If you expect the first code snippet to be faster, the following may be what you may have thought:

A Mistakened Analysis

String is not fundamental to mainstream architectures of modern computers. This means that constructing a long string object is likely slower than looking up a variable. Since the second code snippet constructs the "a..." string 10000 times but the first code snippet only constructs it once, the first code snippet is faster.

Why Are They Similar in Running Time?

The mistake in the analysis above is that it fails to account for the immutability of JavaScript primitives. Since a string is a JavaScript primitive, a string is never modified. Hence, when an optimized JavaScript engine executes the second code snippet, it is able to cache the constructed "a..." string and reuse it in subsequent iterations. This is not necessarily true if the "a..." string were an object. In this case, to optimize, the JavaScript engine must be smart enough to figure out that the object is never modified, which may or may not be the case.

Verification With V8

We can loosely verify this caching behavior with V8. Execute the following in shell:

node --allow-natives-syntax <<EOF
for (let i = 0; i < 3; ++ i) {
  %DebugPrint("aaaaaaaaaaaaaaaaaaaa")
}
EOF

--allow-natives-syntax enables the V8 intrinsic %DebugPrint (implemented here), which prints the address of the variable among other things. The output repeats the following line 3 times:

DebugPrint: 0x3b6cc0a94429: [String] in OldSpace: #aaaaaaaaaaaaaaaaaaaa

The hexadecimal number after DebugPrint: is the address of the "a..." string and is likely different every time you run the script. The line above is repeated 3 times, which implies that V8 has cached the "a..." string and reused it.

Conclusion

  1. A literal representing an object literal is usually more expensive than looking up a variable in terms of performance. If a line with an object literal is expected to be executed multiple times, Performance-wise it is usually a good idea to consider assigning it to a variable beforehand and using this variable instead. A common situation in which people tend to forget this point is when using no-op functions, such as () => {}.

  2. Regular expression (RegExp) literals also represent objects. Hence, Item 1 applies. If this seems counterintuitive to you, chances are you have conflated the timing of regular expression compilation versus regular expression object creation.

  3. Literals of primitive types can be non-fundamental to the CPU architecture, such as String and BigInt. Hence, such a literal is usually more expensive than looking up a variable. However, Item 1 does not apply because an optimized JavaScript engine is able to cache such literals due to their immutability.