What immutable means
Strings in JavaScript are immutable: once a string exists, none of its characters can be changed, added or removed. Every operation that looks like it edits a string actually builds a new one and leaves the original alone.
Trying to change a character in place shows this:
let message = "Jello world";
message[0] = 'H';
console.log(message); // Jello world
The assignment has no effect. What happens next depends on the mode the code runs in. In ordinary ("sloppy") scripts the failed assignment is ignored silently, as above. In strict mode, which is always on inside ES modules and class bodies, and which a "use strict" line at the top of a file switches on, the same line throws:
TypeError: Cannot assign to read only property '0' of string 'Jello world'
Either way, the string is still "Jello world" afterwards. The same applies to message.length = 3 and to adding a property such as message.foo = 1.
The variable can still change
Immutable does not mean that the variable is fixed. A variable declared with let can be pointed at a different string at any time:
let message = "Jello world";
message = "Hello world";
console.log(message); // Hello world
This does not alter the old string. It creates a second string, "Hello world", and makes message refer to it. The old string is simply no longer used, and JavaScript cleans it up when nothing refers to it. It is the same way a number works: let n = 1; n = 2; does not turn the value 1 into 2, but makes n hold a different value.
Do not confuse this with const. A const variable cannot be reassigned at all, so const c = "hi"; c = "yo"; throws TypeError: Assignment to constant variable. That is about the variable. Immutability is about the string value, and it holds for let and const alike.
Methods return new strings
Every string method leaves the original untouched and returns a new string. The result is lost unless you store it:
const word = "bndrei";
word.toUpperCase(); // the result is thrown away
console.log(word); // bndrei
const loud = word.toUpperCase();
console.log(loud); // BNDREI
console.log(word); // bndrei
Joining works the same way. name += "!" builds a new string and stores it in name. Another variable that held the old string keeps its old value:
let a = "hi";
let b = a;
b += "!";
console.log(a, b); // hi hi!
Replacing a character: the exercise
Someone saved a name with a typo: name holds "bndrei Chiriac" instead of "Andrei Chiriac". Their fix, name[0] = 'A';, tries to change the first character in place, which a string does not allow. The fix is to build a new string and store it in the variable:
- Take everything except the first character.
name.substr(1)returns the text starting at index 1, which is"ndrei Chiriac". (slice(1)does the same and is the modern choice, becausesubstris a legacy method.) - Put the correct first letter in front.
'A' + name.substr(1)joins the two pieces into"Andrei Chiriac". - Assign the new string back to
name. This is a reassignment, which is allowed.
let name = "bndrei Chiriac";
// Change code below:
name = 'A' + name.substr(1);
These versions give the same result: 'A' + name.slice(1), `A${name.slice(1)}` and name.replace("b", "A"). The last one replaces the first match of "b", which is the first letter here.
Changing one character in general
To change the character at index i, join the part before it, the new character and the part after it:
const s = "Jello";
const i = 0;
const fixed = s.slice(0, i) + "H" + s.slice(i + 1);
console.log(fixed); // Hello
When you need to change many characters, turn the string into an array, edit the array and join it back, because arrays are mutable:
const chars = [..."hello"];
chars[0] = "J";
console.log(chars.join("")); // Jello
Why strings are immutable
Immutable strings are safe to share. Two variables can refer to the same text without one of them changing it behind the other's back. They also work as keys in objects and maps, because a key's value can never change after it is stored. A cost is that building a long string by repeating += creates many short-lived strings, although JavaScript engines optimize this well. For very large amounts of text, collecting pieces in an array and calling join("") once is a common alternative.
Common mistakes
- Assigning to an index.
s[0] = 'x'never changes the string. It is ignored in sloppy mode and throws aTypeErrorin strict mode. - Calling a method and ignoring the result.
s.trim(),s.replace(...)ands.toUpperCase()do not changes. Writes = s.trim()if you want the new value kept. - Thinking
constis what makes strings immutable. Aletstring is just as unchangeable.letonly allows the variable to point at another string.
Key takeaways
- You cannot change a character of a string in place. Reading
s[i]works, and writing to it does not. - Reassigning a
letvariable to a new string is fine. - String methods and
+return new strings, so store the result. - To "edit" a string, build a new one with
sliceand+, or go through an array of characters.
