This sheet lists the methods on String.prototype that you use most, with what each returns and what happens on a miss. It is for developers who mix up slice, substring and substr, or who get surprised by emoji lengths and replace only changing the first match. The confusion it clears up: strings are immutable, so every method returns a new value, and lengths count UTF-16 code units, not visible characters.
| Method | Returns | Notes |
|---|---|---|
| `s.length` | Number of UTF-16 code units | A property, not a method |
| `s[i]`, `s.charAt(i)` | One code unit | Out of range: `undefined` and `''` |
| `s.at(i)` | One code unit | Negative counts from the end |
| `s.slice(start, end)` | Substring | Negative indexes count from the end |
| `s.substring(start, end)` | Substring | Negatives become 0, swaps if start is larger |
| `s.substr(start, length)` | Substring | Legacy Annex B feature, avoid it |
| `s.codePointAt(i)` | Code point number | `'😀'.codePointAt(0)` is `128512` |
| `s.charCodeAt(i)` | UTF-16 code unit | `'😀'.charCodeAt(0)` is `55357` |
| Method | Returns | Not found |
|---|---|---|
| `indexOf(text, from)` | First index | `-1` |
| `lastIndexOf(text, from)` | Last index | `-1` |
| `includes(text, from)` | boolean | `false` |
| `startsWith(text, pos)` | boolean | `false` |
| `endsWith(text, len)` | boolean | `false` |
| `search(regex)` | Index of first match | `-1` |
| `match(regex)` | Array, or all matches with `g` | `null` |
| `matchAll(regex)` | Iterator of match objects, needs `g` | empty iterator |
| Method | Returns |
|---|---|
| `replace(pattern, replacement)` | New string, first match only when the pattern is a string |
| `replaceAll(pattern, replacement)` | New string, every match |
| `toUpperCase()`, `toLowerCase()` | Case-changed copy |
| `trim()`, `trimStart()`, `trimEnd()` | Whitespace removed |
| `padStart(length, fill)`, `padEnd(length, fill)` | Padded to a target length |
| `repeat(count)` | Concatenated copies |
| `concat(...parts)` | Joined string, `+` and template literals are usual |
| `split(separator, limit)` | Array of pieces |
| `normalize(form)` | Unicode-normalized string, default `NFC` |
| `localeCompare(other, locales, options)` | Negative, 0 or positive |
| `isWellFormed()`, `toWellFormed()` | Lone-surrogate check and repair |
| Token | Inserts |
|---|---|
| `$$` | A literal `$` |
| `$&` | The matched text |
| `$1` to `$99` | A numbered capture group |
| `` $` `` | Text before the match |
| `$'` | Text after the match |
| `$<name>` | A named capture group |
| Method | Available across browsers since |
|---|---|
| `trim`, `split` | July 2015 |
| `normalize` | September 2016 |
| `padStart`, `padEnd` | April 2017 |
| `matchAll` | January 2020 |
| `replaceAll` | August 2020 |
| `at` | March 2022 |
| `isWellFormed`, `toWellFormed` | October 2023 |
Dates come from MDN's Baseline notes. Check your own browser support policy before relying on the newest methods.
const s = 'Hello, World';
console.log(s.slice(-5), s.slice(0, 5), s.slice(7, -1));
console.log(s.substring(5, 0), s.substring(-3, 5), s.slice(5, 0) === '');
World Hello Worl
Hello Hello true
slice takes negative numbers literally as offsets from the end and returns '' if start is past end. substring swaps its arguments and clamps negatives to 0, which hides mistakes. Prefer slice.
console.log('a-b-c'.replace('-', '+'));
console.log('a-b-c'.replaceAll('-', '+'));
console.log('John Smith'.replace(/(\w+) (\w+)/, '$2, $1'));
console.log('abc'.replace(/b/, m => m.toUpperCase()));
a+b-c
a+b+c
Smith, John
aBc
A string pattern replaces only the first match. replaceAll replaces all, and a function replacement receives the match and its groups.
console.log('5'.padStart(3, '0'), 'ab'.padEnd(5, '-') + '|', '12345'.padStart(3, '0'));
console.log('x'.padStart(5, 'ab'));
005 ab---| 12345
ababx
Padding never truncates, so a longer string comes back unchanged. The fill string repeats and is cut to fit.
console.log('a,b,,c'.split(','), 'a,b,c'.split(',', 2));
console.log('a1b2'.split(/\d/), 'a1b2'.split(/(\d)/));
console.log(''.split(','), ''.split(''));
[ 'a', 'b', '', 'c' ] [ 'a', 'b' ]
[ 'a', 'b', '' ] [ 'a', '1', 'b', '2', '' ]
[ '' ] []
Consecutive separators produce empty strings. Capture groups in a regex separator are kept in the result. An empty string split by a comma gives [''], not [].
console.log([...'a1b22c333'.matchAll(/\d+/g)].map(m => [m[0], m.index]));
console.log('abc'.match(/(?<first>a)/).groups.first);
[ [ '1', 1 ], [ '22', 3 ], [ '333', 6 ] ]
a
matchAll gives every match with its index and groups, where match with g gives only the strings. Try patterns in the Regex Tester.
console.log('a'.localeCompare('B'), 'a' < 'B');
console.log(['10', '9', '2'].sort());
console.log(['10', '9', '2'].sort((a, b) => a.localeCompare(b, undefined, {numeric: true})));
console.log('ä'.localeCompare('z', 'de'), 'ä'.localeCompare('z', 'sv'));
-1 false
[ '10', '2', '9' ]
[ '2', '9', '10' ]
-1 1
The < operator compares code units, so 'B' sorts before 'a'. localeCompare follows the language, and in Swedish ä sorts after z.
console.log('hello world'.replace(/\b\w/g, c => c.toUpperCase()));
console.log('camelCaseString'.replace(/([A-Z])/g, ' $1').toLowerCase());
console.log('tEsT'.charAt(0).toUpperCase() + 'tEsT'.slice(1).toLowerCase());
Hello World
camel case string
Test
For more naming styles, the Case Converter handles camelCase, snake_case and Title Case.
const slug = s => s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim()
.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
console.log(slug(' Crème Brûlée & Café! '));
const trunc = (s, n) => s.length > n ? s.slice(0, n - 1) + '…' : s;
console.log(trunc('JavaScript strings', 10));
creme-brulee-cafe
JavaScrip…
normalize('NFD') splits accented letters into a base letter plus a combining mark, and the first regex strips the marks. The truncation counts code units, so it can cut an emoji in half. Use Array.from(s).slice(0, n) if the text may hold emoji.
'😀'.length is 2 and [...'😀'].length is 1, because indexing and length use UTF-16 code units. Spread or Array.from to count code points. Even that splits some emoji sequences and combining marks.split(''): 'a😀b'.split('').reverse().join('') breaks the emoji into two halves, while [...'a😀b'].reverse().join('') gives b😀a.'é' is 1 code unit and 'é' is 2, so 'é' === 'é' is false. Call normalize() on both sides before comparing.replaceAll with a non-global regex: 'a-b'.replaceAll(/-/, '+') throws TypeError: String.prototype.replaceAll called with a non-global RegExp argument. Add the g flag or use a string.'abc'.replace('b', '$&$&') gives abbc, and 'x'.replace('x', '$$') gives $. When inserting user text, pass a function: s.replace(x, () => userText).t[0] = 'X' on a string does nothing in sloppy mode, and in strict mode throws Cannot assign to read only property '0' of string 'abc'. Assign the result of the method back to a variable.'ß'.toUpperCase() is 'SS', and 'İ'.toLowerCase().length is 2. Do not assume the length is unchanged, and use localeCompare with sensitivity: 'base' for caseless comparison.substr and argument order: substr(start, length) takes a length where slice(start, end) takes an end index. MDN calls substr an Annex B feature and advises slice or substring instead.split(',') on empty input: it returns [''] with one item, so a loop over the result runs once. Filter empties or check the input first.'abc' == new String('abc') is true, but typeof new String('abc') is object. Avoid the new String constructor.match, replace and splitrepeat() with a custom joinerjoin, map and reverse pair with split