Skip to content
JavaScript js strings 4 min read

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.

MethodSecond argumentNegative indicesStatus
slice(start, end)end index (exclusive)supportedPreferred
substring(start, end)end index (exclusive)clamped to 0, args swappedOK
substr(start, length)lengthstart onlyDeprecated

Reach for slice by default. It behaves the same way as Array.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 slice over substring and never use the deprecated substr.
  • Always trim() user input before validating, comparing, or storing it.
  • Use padStart/padEnd instead 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 of str[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.
Last updated June 1, 2026
Was this helpful?