Does javascript use immutable or mutable strings? Do I need a "string builder"?
-
5Yes, the y are immutable and you need a "string builder" of some sort. Read this blog.codeeffects.com/Article/String-Builder-In-Java-Script or this codeproject.com/KB/scripting/stringbuilder.aspxKizz– Kizz2011-07-14 20:14:50 +00:00Commented Jul 14, 2011 at 20:14
-
4Interesting, those examples contradict my findings in my answer.Ruan Mendes– Ruan Mendes2012-02-23 19:29:13 +00:00Commented Feb 23, 2012 at 19:29
-
Here is an ES stringbuilderKooiInc– KooiInc2025-09-21 08:24:39 +00:00Commented Sep 21 at 8:24
10 Answers
They are immutable. You cannot change a character within a string with something like var myString = "abbdef"; myString[2] = 'c'
. The string manipulation methods such as trim
, slice
return new strings.
In the same way, if you have two references to the same string, modifying one doesn't affect the other
let a = b = "hello";
a = a + " world";
// b is not affected
Myth Debunking - String concatenation is NOT slow
I've always heard what Ash mentioned in his answer (that using Array.join is faster for concatenation) so I wanted to test out the different methods of concatenating strings and abstracting the fastest way into a StringBuilder. I wrote some tests to see if this is true (it isn't!).
This was what I believed would be the fastest way, avoiding push and using an array to store the strings to then join them in the end.
class StringBuilderArrayIndex {
array = [];
index = 0;
append(str) {
this.array[this.index++] = str
}
toString() {
return this.array.join('')
}
}
Some benchmarks
- Read the test cases in the snippet below
- Run the snippet
- Press the benchmark button to run the tests and see results
I've created two types of tests
- Using Array indexing to avoid
Array.push
, then usingArray.join
- Straight string concatenation
For each of those tests, I looped appending a constant value and a random string;
<script benchmark>
// Number of times to loop through, appending random chars
const APPEND_COUNT = 1000;
const STR = 'Hot diggity dizzle';
function generateRandomString() {
const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const length = Math.floor(Math.random() * 10) + 1; // Random length between 1 and 10
let result = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
result += characters.charAt(randomIndex);
}
return result;
}
const randomStrings = Array.from({length: APPEND_COUNT}, generateRandomString);
class StringBuilderStringAppend {
str = '';
append(str) {
this.str += str;
}
toString() {
return this.str;
}
}
class StringBuilderArrayIndex {
array = [];
index = 0;
append(str) {
this.array[this.index] = str;
this.index++;
}
toString() {
return this.array.join('');
}
}
// @group Same string 'Hot diggity dizzle'
// @benchmark array push & join
{
const sb = new StringBuilderArrayIndex();
for (let i = 0; i < APPEND_COUNT; i++) {
sb.append(STR)
}
sb.toString();
}
// @benchmark string concatenation
{
const sb = new StringBuilderStringAppend();
for (let i = 0; i < APPEND_COUNT; i++) {
sb.append(STR)
}
sb.toString();
}
// @group Random strings
// @benchmark array push & join
{
const sb = new StringBuilderArrayIndex();
for (let i = 0; i < APPEND_COUNT; i++) {
sb.append(randomStrings[i])
}
sb.toString();
}
// @benchmark string concatenation
{
const sb = new StringBuilderStringAppend();
for (let i = 0; i < APPEND_COUNT; i++) {
sb.append(randomStrings[i])
}
sb.toString();
}
</script>
<script src="https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js"></script>
Findings
Nowadays, all evergreen browsers handle string concatenation better, at least twice as fast.
i-12600k (added by Alexander Nenashev)
Chrome/117
--------------------------------------------------------------------
Same string 'Hot diggity dizzle'
string concatenation 1.0x | x100000 224 232 254 266 275
array push & join 3.2x | x100000 722 753 757 762 763
Random strings
string concatenation 1.0x | x100000 261 268 270 273 279
array push & join 5.4x | x10000 142 147 148 155 166
--------------------------------------------------------------------
https://github.com/silentmantra/benchmark
Firefox/118
--------------------------------------------------------------------
Same string 'Hot diggity dizzle'
string concatenation 1.0x | x100000 304 335 353 358 370
array push & join 9.5x | x10000 289 300 301 306 309
Random strings
string concatenation 1.0x | x100000 334 337 345 349 377
array push & join 5.1x | x10000 169 176 176 176 180
--------------------------------------------------------------------
https://github.com/silentmantra/benchmark
Results below on a 2.4 GHz 8-Core i9 Mac on Oct 2023
Chrome
--------------------------------------------------------------------
Same string 'Hot diggity dizzle'
string concatenation 1.0x | x100000 574 592 594 607 613
array push & join 2.7x | x10000 156 157 159 164 165
Random strings
string concatenation 1.0x | x100000 657 663 669 675 680
array push & join 4.3x | x10000 283 285 295 298 311
--------------------------------------------------------------------
https://github.com/silentmantra/benchmark
Firefox
--------------------------------------------------------------------
Same string 'Hot diggity dizzle'
string concatenation 1.0x | x100000 546 648 659 663 677
array push & join 5.8x | x10000 314 320 326 331 335
Random strings
string concatenation 1.0x | x100000 647 739 764 765 804
array push & join 2.9x | x10000 187 188 199 219 231
--------------------------------------------------------------------
https://github.com/silentmantra/benchmark
Brave
--------------------------------------------------------------------
Same string 'Hot diggity dizzle'
string concatenation 1.0x | x100000 566 571 572 579 600
array push & join 2.5x | x10000 144 145 159 162 166
Random strings
string concatenation 1.0x | x100000 649 658 659 663 669
array push & join 4.4x | x10000 285 285 290 292 300
--------------------------------------------------------------------
https://github.com/silentmantra/benchmark `
Safari
--------------------------------------------------------------------
Same string 'Hot diggity dizzle'
string concatenation 1.0x | x10000 76 77 77 79 82
array push & join 2.2x | x10000 168 168 174 178 186
Random strings
string concatenation 1.0x | x100000 878 884 889 892 903
array push & join 2.3x | x10000 199 200 202 202 204
--------------------------------------------------------------------
https://github.com/silentmantra/benchmark `
Opera
--------------------------------------------------------------------
Same string 'Hot diggity dizzle'
string concatenation 1.0x | x100000 577 579 581 584 608
array push & join 2.7x | x10000 157 162 165 166 171
Random strings
string concatenation 1.0x | x100000 688 694 740 750 781
array push & join 4.2x | x10000 291 315 316 317 379
--------------------------------------------------------------------
https://github.com/silentmantra/benchmark
13 Comments
Exploder
with D. I see what you did here. :Dfrom the rhino book:
In JavaScript, strings are immutable objects, which means that the characters within them may not be changed and that any operations on strings actually create new strings. Strings are assigned by reference, not by value. In general, when an object is assigned by reference, a change made to the object through one reference will be visible through all other references to the object. Because strings cannot be changed, however, you can have multiple references to a string object and not worry that the string value will change without your knowing it
11 Comments
null
undefined
number
and boolean
. Strings are assigned by value and not by reference and are passed as such. Thus, strings are not just immutable, they are a value. Changing the string "hello"
to be "world"
is like deciding that from now on the number 3 is the number 4... it makes no sense.var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
String
objects created using the string constructor are wrappers around JavaScript string values. You can access the string value of the boxed type using the .valueOf()
function - this is also true for Number
objects and number values. It's important to note String
objects created using new String
are not actual strings but are wrappers or boxes around strings. See es5.github.io/#x15.5.2.1 . About how things convert to objects see es5.github.io/#x9.9Just to clarify for simple minds like mine (from MDN):
Immutables are the objects whose state cannot be changed once the object is created.
String and Numbers are Immutable.
Immutable means that:
You can make a variable name point to a new value, but the previous value is still held in memory. Hence the need for garbage collection.
var immutableString = "Hello";
// In the above code, a new object with string value is created.
immutableString = immutableString + "World";
// We are now appending "World" to the existing value.
This looks like we're mutating the string 'immutableString', but we're not. Instead:
On appending the "immutableString" with a string value, following events occur:
- Existing value of "immutableString" is retrieved
- "World" is appended to the existing value of "immutableString"
- The resultant value is then allocated to a new block of memory
- "immutableString" object now points to the newly created memory space
- Previously created memory space is now available for garbage collection.
4 Comments
Performance tip:
If you have to concatenate large strings, put the string parts into an array and use the Array.Join()
method to get the overall string. This can be many times faster for concatenating a large number of strings.
There is no StringBuilder
in JavaScript.
5 Comments
The string type value is immutable, but the String object, which is created by using the String() constructor, is mutable, because it is an object and you can add new properties to it.
> var str = new String("test")
undefined
> str
[String: 'test']
> str.newProp = "some value"
'some value'
> str
{ [String: 'test'] newProp: 'some value' }
Meanwhile, although you can add new properties, you can't change the already existing properties
A screenshot of a test in Chrome console
In conclusion, 1. all string type value (primitive type) is immutable. 2. The String object is mutable, but the string type value (primitive type) it contains is immutable.
6 Comments
new String
generates a mutable wrapper around an immutable stringString
object (wrapper), meaning it is not immutable (by default; like any other object you can call Object.freeze
on it to render it immutable). But a primitive string value type, whether contained in a String
object wrapper or not, is always immutable.It's a late post, but I didn't find a good book quote among the answers.
Here's a definite except from a reliable book:
Strings are immutable in ECMAScript, meaning that once they are created, their values cannot change. To change the string held by a variable, the original string must be destroyed and the variable filled with another string containing a new value... —Professional JavaScript for Web Developers, 3rd Ed., p.43
Now, the answer which quotes Rhino book's excerpt is right about string immutability but wrong saying "Strings are assigned by reference, not by value." (probably they originally meant to put the words an opposite way).
The "reference/value" misconception is clarified in the "Professional JavaScript", chapter named "Primitive and Reference values":
The five primitive types...[are]: Undefined, Null, Boolean, Number, and String. These variables are said to be accessed by value, because you are manipulating the actual value stored in the variable. —Professional JavaScript for Web Developers, 3rd Ed., p.85
that's opposed to objects:
When you manipulate an object, you’re really working on a reference to that object rather than the actual object itself. For this reason, such values are said to be accessed by reference.—Professional JavaScript for Web Developers, 3rd Ed., p.85
1 Comment
Regarding your question (in your comment to Ash's response) about the StringBuilder in ASP.NET Ajax the experts seem to disagree on this one.
Christian Wenz says in his book Programming ASP.NET AJAX (O'Reilly) that "this approach does not have any measurable effect on memory (in fact, the implementation seems to be a tick slower than the standard approach)."
On the other hand Gallo et al say in their book ASP.NET AJAX in Action (Manning) that "When the number of strings to concatenate is larger, the string builder becomes an essential object to avoid huge performance drops."
I guess you'd need to do your own benchmarking and results might differ between browsers, too. However, even if it doesn't improve performance it might still be considered "useful" for programmers who are used to coding with StringBuilders in languages like C# or Java.
Comments
JavaScript strings are indeed immutable.