Essential String Methods
Strings are immutable in JavaScript, so every “string method” returns a brand-new string rather than modifying the original. That single fact shapes how you work with text: you chain methods, capture results in new variables, and never worry about side effects. This page covers the methods you will reach for daily — extracting substrings, changing case, trimming whitespace, padding, repeating, and indexing with at.
Extracting substrings: slice, substring, substr
There are three ways to pull a piece out of a string, and the difference between them trips up almost everyone. The modern, recommended choice is slice(start, end). It returns the characters from start up to (but not including) end, and it accepts negative indices that count back from the end.
const text = "JavaScript";
console.log(text.slice(0, 4)); // from start, 4 chars
console.log(text.slice(4)); // to the end
console.log(text.slice(-6)); // last 6 chars
console.log(text.slice(-6, -2)); // negative range
Output:
Java
Script
Script
Scri
substring(start, end) looks similar but treats negative arguments as 0 and swaps the two indices if start is greater than end. That auto-swap occasionally helps, but it more often hides bugs.
substr(start, length) takes a length as its second argument instead of an end index. It is legacy and formally deprecated, so avoid it in new code.
| Method | Second argument | Negative indices | Status |
|---|---|---|---|
slice(start, end) | end index (exclusive) | supported | Preferred |
substring(start, end) | end index (exclusive) | clamped to 0, args swapped | OK |
substr(start, length) | length | start only | Deprecated |
Reach for
sliceby default. It behaves the same way asArray.prototype.slice, so you only have to remember one mental model for both strings and arrays.
Changing case
Case conversion is straightforward, but the locale-aware variants matter for international text. toUpperCase() and toLowerCase() work on the Unicode default mappings, while toLocaleUpperCase() and toLocaleLowerCase() respect language-specific rules (famously, the dotless Turkish i).
const name = "Ada Lovelace";
console.log(name.toUpperCase());
console.log(name.toLowerCase());
console.log("istanbul".toLocaleUpperCase("tr-TR")); // Turkish rules
Output:
ADA LOVELACE
ada lovelace
İSTANBUL
JavaScript has no built-in capitalize, but you can build one cleanly by combining charAt/slice with case methods:
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
console.log(capitalize("hELLO"));
console.log(capitalize("javaScript"));
Output:
Hello
Javascript
Trimming whitespace
trim() removes whitespace from both ends of a string — spaces, tabs, newlines, and other Unicode whitespace. When you only want one side, use trimStart() or trimEnd(). None of them touch interior whitespace.
const raw = " hello world ";
console.log(`[${raw.trim()}]`);
console.log(`[${raw.trimStart()}]`);
console.log(`[${raw.trimEnd()}]`);
Output:
[hello world]
[hello world ]
[ hello world]
Trimming is essential for cleaning user input — always trim() form fields before validating or storing them.
Padding: padStart and padEnd
Introduced in ES2017, padStart(targetLength, padString) and padEnd(targetLength, padString) grow a string to a target length by repeatedly adding a fill string. They are perfect for aligning columns, formatting time, or zero-padding numbers. If the string is already at least targetLength, it is returned unchanged.
console.log("5".padStart(3, "0")); // zero-pad a number
console.log("42".padEnd(6, ".")); // trailing fill
console.log("7:5".padStart(5, "0")); // partial fill string
const minutes = 9;
console.log(String(minutes).padStart(2, "0")); // clock formatting
Output:
005
42....
007:5
09
The pad string defaults to a single space, so "x".padStart(4) yields " x". Note that the fill is truncated to fit exactly — it will not overshoot the target length.
Repeating with repeat
repeat(count) returns the string concatenated count times. It is handy for building separators, indentation, or simple ASCII output. A negative or non-integer-overflowing count throws a RangeError.
console.log("=".repeat(20));
console.log("ab".repeat(3));
console.log(" ".repeat(2) + "indented");
Output:
====================
ababab
indented
Indexing characters with at
You can read a single character with bracket notation (str[0]) or charAt(0), but ES2022 added at(index), which — like slice — accepts negative indices. That makes grabbing the last character trivial.
const word = "documentation";
console.log(word.at(0)); // first
console.log(word.at(-1)); // last
console.log(word[0]); // bracket access still works
console.log(word.charAt(99)); // out of range -> empty string
Output:
d
n
d
The key difference: at(-1) returns the last character, whereas word[-1] returns undefined, and charAt(-1) returns an empty string. Use at when you need end-relative access.
Best practices
- Prefer
sliceoversubstringand never use the deprecatedsubstr. - Always
trim()user input before validating, comparing, or storing it. - Use
padStart/padEndinstead of hand-rolled loops for zero-padding and alignment. - Remember strings are immutable — capture the returned value; methods never mutate in place.
- Reach for
at(-1)to get the last character cleanly instead ofstr[str.length - 1]. - Use the
toLocale*case methods when handling user-facing international text. - Chain methods freely (
str.trim().toLowerCase()) since each returns a fresh string.