String methods.
slice, split, replace, includes.
Strings have 30+ methods. Six do 90% of the work: slice, split, replace, includes, trim, and the case converters. This page covers what each does, the gotchas, and the patterns that come up in real code — like forgetting that replace only replaces the first match, or that strings are immutable so the result must be reassigned.
Short answer: slice(start, end) extracts a portion, supports negative indices. split(sep) breaks into an array. replace(a, b) replaces the first match — use replaceAll or a global regex for all. includes(str) returns a boolean. trim() removes whitespace. Strings are immutable — every method returns a new string, so assign the result. String comparison is case-sensitive and locale-blind — use toLowerCase() or localeCompare.
✅ The six you'll use daily
What each does, in one line.
slice(start, end)
Extracts a substring. Negative indices from the end. 'hello'.slice(-3) → 'llo'.
split(sep)
Breaks into an array. 'a,b,c'.split(',') → ['a','b','c']. Split on '' to get characters.
replace(a, b)
Replaces the first match only. Use replaceAll or /a/g for all. Strings are immutable.
includes(str)
Returns true if the substring exists. Case-sensitive. For case-insensitive, lowercase both sides.
trim()
Removes whitespace from both ends. trimStart() and trimEnd() for one side only.
toUpperCase() / toLowerCase()
Changes case. Used for case-insensitive comparison: a.toLowerCase() === b.toLowerCase().
✅ The methods in action
A realistic string, six operations.
const str = ' Hello, World! ';
// slice — extract a portion
console.log(str.slice(2, 7)); // 'Hello'
console.log(str.slice(-3)); // '! ' (last 3 chars)
console.log(str.slice(2)); // 'Hello, World! '
// split — break into an array
console.log('a,b,c'.split(',')); // ['a', 'b', 'c']
console.log('hello'.split('')); // ['h', 'e', 'l', 'l', 'o']
console.log(str.split(' ')); // ['', '', 'Hello,', 'World!', '', '']
// replace — first match only
console.log(str.replace('o', '0')); // ' Hell0, World! '
console.log(str.replaceAll('o', '0')); // ' Hell0, W0rld! '
// includes
console.log(str.includes('World')); // true
console.log(str.includes('world')); // false (case-sensitive)
// trim
console.log(str.trim()); // 'Hello, World!'
console.log(str.trimStart()); // 'Hello, World! '
console.log(str.trimEnd()); // ' Hello, World!'
// case
console.log(str.trim().toUpperCase()); // 'HELLO, WORLD!'
console.log(str.trim().toLowerCase()); // 'hello, world!'
Key details:
slice:
slice(2, 7) → from index 2 up to (not including) index 7
slice(-3) → from index length-3 to the end
slice(2) → from index 2 to the end
Out-of-range indices are clamped, not thrown.
split:
split(',') → split on every comma
split('') → split into individual characters
split(' ') → split on every space (multiple spaces = empty strings)
If the separator isn't found, returns [wholeString].
replace:
replace('o', '0') → first 'o' only
replace(/o/g, '0') → all 'o' (regex with g flag)
replaceAll('o', '0') → all 'o' (simpler syntax)
The replacement can be a function: replace('o', () => '0')
includes:
Case-sensitive by default.
For case-insensitive:
str.toLowerCase().includes(sub.toLowerCase())
For regex, use str.match(/pattern/) or str.search(/pattern/).
trim:
Removes spaces, tabs, newlines, and Unicode whitespace.
Non-whitespace characters (including zero-width spaces) are preserved.
trimStart and trimEnd for one-sided trim.
Case:
toUpperCase / toLowerCase are locale-insensitive.
For Turkish (dotless i), use toLocaleUpperCase('tr').
For most code, the simple versions are fine.
Strings are IMMUTABLE.
Every method returns a new string.
The original is unchanged.
❌ str.toUpperCase(); // result discarded
✅ str = str.toUpperCase(); // result kept
✅ The gotchas
What actually trips people up.
// ❌ replace only replaces the first match
console.log('a-a-a'.replace('a', 'b')); // 'b-a-a'
// ✅ Use replaceAll or a global regex
console.log('a-a-a'.replaceAll('a', 'b')); // 'b-b-b'
console.log('a-a-a'.replace(/a/g, 'b')); // 'b-b-b'
// ❌ Strings are immutable — result must be reassigned
let s = 'hello';
s.toUpperCase();
console.log(s); // 'hello' (unchanged)
s = s.toUpperCase();
console.log(s); // 'HELLO'
// ❌ Case-sensitive includes
console.log('Hello'.includes('hello')); // false
// ✅ Lowercase both sides for case-insensitive
console.log('Hello'.toLowerCase().includes('hello'.toLowerCase()));
// true
// ❌ slice and substring differ on negatives
console.log('hello'.slice(-3)); // 'llo'
console.log('hello'.substring(-3)); // 'hello'
// ✅ slice is the modern default
// ❌ Comparing strings with > or < is code-unit based
console.log('Z' < 'a'); // true (uppercase before lowercase)
// ✅ Use localeCompare for locale-aware comparison
console.log('Z'.localeCompare('a')); // 1 (Z > a in en locale)
The five gotchas to know:
1. replace only replaces first match.
For all: replaceAll (ES2021) or /pattern/g.
The most common string bug in JavaScript.
2. Strings are immutable.
Every method returns a new string.
The original is unchanged.
Always assign: str = str.method().
3. includes is case-sensitive.
For case-insensitive: toLowerCase() both sides.
There's no includesIgnoreCase.
4. slice vs substring differ on negatives.
slice(-3) → last 3 chars.
substring(-3) → whole string (treats negative as 0).
Use slice. Ignore substring.
5. String comparison is code-unit based.
'Z' < 'a' is true (uppercase before lowercase).
For locale-aware: localeCompare.
For case-insensitive equality:
a.toLowerCase() === b.toLowerCase().
Bonus: split on a single space
'a b'.split(' ') → ['a', '', 'b']
Multiple spaces produce empty strings.
If you want any whitespace:
str.split(/\s+/)
This splits on one or more whitespace characters.
Bonus: string.length is UTF-16 code units
'😀'.length === 2 (surrogate pair)
For grapheme count: [...str].length
For characters as user sees them: use Intl.Segmenter
These aren't bugs — they're the spec.
Knowing them saves debugging time.
✅ Chaining
String methods chain naturally.
// Chaining — each returns a new string or value
const result = ' Hello, World! '
.trim() // 'Hello, World!'
.toLowerCase() // 'hello, world!'
.replace('world', 'JS') // 'hello, JS!'
.split(', ') // ['hello', 'JS!']
.join(' & '); // 'hello & JS!'
console.log(result);
// 'hello & JS!'
// Without chaining — same result, more code
let a = ' Hello, World! ';
a = a.trim();
a = a.toLowerCase();
a = a.replace('world', 'JS');
a = a.split(', ');
a = a.join(' & ');
// Both work. Chaining is more readable for transformations.
// Real-world: format a slug
function slugify(str) {
return str
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '') // remove special chars
.replace(/\s+/g, '-') // spaces to hyphens
.replace(/-+/g, '-'); // collapse multiple hyphens
}
console.log(slugify('Hello, World!')); // 'hello-world'
console.log(slugify(' My Post! ')); // 'my-post'
Why chaining works:
Every string method that "modifies" returns
a new string. So you can call another method
on the result.
str.trim() → returns a new string
.toLowerCase() → returns a new string
.replace(...) → returns a new string
.split(...) → returns an array
.join(...) → returns a string
The chain reads left to right, top to bottom.
When to chain vs when to break up:
✅ Chain when the transformations are short:
str.trim().toLowerCase()
✅ Chain when each step is obvious:
str.trim().toLowerCase().replace(...)
❌ Break when a step needs a comment:
str
.trim()
.toLowerCase()
// step 3: remove all non-word chars
.replace(/[^\w\s-]/g, '')
❌ Break when the chain is > 4-5 steps
— debugging a long chain is hard
String chaining is one of the most-used
patterns in JavaScript. Every URL parser,
config loader, and form handler uses it.
Once you know the methods, the chains
write themselves.
Don't forget that replace only replaces the first match. This is the most common string bug in JavaScript. 'a-a-a'.replace('a', 'b') gives 'b-a-a', not 'b-b-b'. Every developer has shipped this bug at least once. The fix is one of three: replaceAll('a', 'b') (ES2021, all modern environments), replace(/a/g, 'b') (regex with global flag, works everywhere), or split('a').join('b') (works in older environments). If you're replacing a string, be explicit — replace with a string is first-match-only, replaceAll or /g replaces everything.
⚠️ Common mistakes
The string method mistakes that cost real time.
replace('a', 'b') for all matchesOnly replaces the first. Fix: replaceAll or /a/g.
Strings are immutable — the original is unchanged. Fix: str = str.method().
includes to be case-insensitiveIt's case-sensitive. Fix: toLowerCase() both sides.
substring with negative indicesTreats negatives as 0. Fix: use slice.
< and >Code-unit based. Fix: localeCompare for locale-aware.
Multiple spaces produce empty strings. Fix: split(/\s+/).
.length is character countUTF-16 code units — emoji count as 2. Fix: [...str].length for code points.
Hard to debug. Fix: break when the chain exceeds 4-5 steps.
❓ Frequently asked
What are the most important JavaScript string methods?
The core set: slice, split, replace, includes, trim, toUpperCase, toLowerCase, padStart. Most real-world string work uses five or six of them. The array methods map, filter, reduce are for arrays; for strings, these are the equivalents.
What is the difference between slice and substring?
Both extract part of a string, but they handle negative values differently. slice accepts negative indices — 'hello'.slice(-3) gives 'llo'. substring treats negative indices as 0 — 'hello'.substring(-3) gives 'hello'. The recommendation: use slice. It's more predictable.
How do I replace all occurrences of a string?
Three ways. (1) replace with a global regex: str.replace(/foo/g, 'bar'). (2) replaceAll (ES2021): str.replaceAll('foo', 'bar'). (3) split and join: str.split('foo').join('bar'). The gotcha: replace with a string only replaces the first occurrence.
Why does string comparison fail?
Strings compare by UTF-16 code unit, not by locale. 'Z' < 'a' is true because uppercase letters come before lowercase in ASCII. For locale-aware comparison, use localeCompare. For case-insensitive comparison, lowercase both strings first.
What is the difference between trim and trimStart?
trim() removes whitespace from both ends. trimStart() removes from the beginning only. trimEnd() removes from the end only. All three methods handle the full range of whitespace characters.
Are strings mutable in JavaScript?
No. Strings are immutable — any method that 'modifies' a string actually returns a new one. str.toUpperCase() does not change str; it returns a new string. The consequence: you must assign the result: str = str.toUpperCase().
🔗 Continue learning
Next in this series: