JavaScript strings are immutable, so every method here returns a new string or a
derived value rather than changing the original. This reference lists each
String.prototype method with its signature, return type, and the unicode
gotchas that trip people up.
The one rule that prevents most string bugs
Strings are immutable. No String.prototype method ever changes the original string. Every method returns a new value, and if you do not assign that return value, it is lost silently. This trips up developers coming from languages where string mutation is common:
let name = " alice ";
name.trim(); // does nothing visible — return value discarded
console.log(name); // " alice " — unchanged
name = name.trim(); // correct — assign the return value
console.log(name); // "alice"
Method groups
- Search —
indexOf,lastIndexOf,includes,startsWith,endsWith,match,matchAll,search— locate text or patterns. - Slice —
slice,substring,substr,at— extract a portion of the string. - Transform —
toUpperCase,toLowerCase,toLocaleLowerCase,trim,trimStart,trimEnd,padStart,padEnd,replace,replaceAll,repeat,normalize— produce a new transformed string. - Convert —
split,charAt,charCodeAt,codePointAt,fromCharCode,localeCompare— move between strings, arrays, and numeric code points.
Because strings are immutable, none of these mutate this; you always keep the return value.
The slice vs substring vs substr trap
These three look similar but behave differently at the edges:
| Method | Negative indices | Argument meaning |
|---|---|---|
slice(start, end) | Counts from end of string | Start and end positions |
substring(start, end) | Clamps negatives to 0; swaps args if start > end | Start and end positions |
substr(start, length) | Negative start counts from end | Start position and length |
substr is legacy and its removal from the standard has been discussed. Prefer slice for predictable behaviour with negative indices.
The replace vs replaceAll trap
replace with a string pattern only replaces the first match. replaceAll replaces every occurrence. Forgetting this is one of the most common JavaScript string bugs:
"foo foo foo".replace("foo", "bar") // "bar foo foo"
"foo foo foo".replaceAll("foo", "bar") // "bar bar bar"
If you pass a regex to replaceAll, it must have the g (global) flag or a TypeError is thrown.
Unicode pitfalls
length counts UTF-16 code units, not characters. Characters outside the Basic Multilingual Plane (many emoji, some CJK extension characters) are encoded as two code units (a surrogate pair). A single emoji can have a .length of 2.
"A".length // 1
"😀".length // 2
To count visible characters: [..."😀"].length gives 1 because spread and for...of iterate over Unicode code points, not code units.
normalize for reliable comparison. The same visible character can have multiple Unicode representations — for example “é” as a single precomposed code point or as “e” followed by a combining accent. They look identical but are not === equal. Always normalise before comparison or search:
"café".normalize("NFC") === "café".normalize("NFC") // true even with different source encodings
Practical decision guide
- Need the position of a match? →
indexOf/lastIndexOf - Just need to know if a string is present? →
includes(cleaner thanindexOf !== -1) - Extracting a portion? →
slice(prefer oversubstring) - Replacing all occurrences of a literal string? →
replaceAll - Padding a number string to fixed width? →
padStart - Splitting into words or tokens? →
split - Comparing user-entered text that might have different Unicode encodings? →
normalize('NFC')first