Strings come with built-in methods: small tools you call with a dot and parentheses, like name.toUpperCase(). Because strings are immutable, every string method returns a new value and leaves the original untouched. Keep that one rule in mind and most string bugs disappear.
let s = "Hello World";
let loud = s.toUpperCase();
console.log(loud);
console.log(s); // unchanged
s.toUpperCase(); on a line by itself does nothing useful: it makes an uppercase copy and throws it away. Keep the result: s = s.toUpperCase();. And do not forget the parentheses. s.toUpperCase without () does not call the method at all; it just refers to it.
let text = "Hello World!";
console.log(text.toUpperCase());
console.log(text.toLowerCase());
a.toLowerCase() === b.toLowerCase(). That is how "YES", "Yes", and "yes" can all count as the same answer.
There are four ways to get a single character out of a string:
let name = "W3Schools";
console.log(name[2]); // property access, like an array
console.log(name.charAt(2)); // the classic method
console.log(name.at(2)); // the modern method (ES2022)
console.log(name.at(-5)); // negative: counts back from the end
at() is the only one that accepts negative numbers, which makes "the last character" easy: name.at(-1) instead of name[name.length - 1].
The three are almost the same, but differ when the position does not exist:
let text = "HELLO WORLD";
console.log(text[20]); // undefined
console.log(text.charAt(20)); // "" (an empty string)
Square brackets also make a string look like an array, but it is not one: you cannot change a character with text[0] = "A" (strings are immutable).
Every character is stored as a number. charCodeAt() gives that number (its UTF-16 code, between 0 and 65535):
let text = "HELLO WORLD";
console.log(text.charCodeAt(0)); // the code for "H"
console.log(text.codePointAt(0)); // same here
These codes are why "Zebra" < "apple" is true: Z is 90 and a is 97. codePointAt() gives the same answer for ordinary characters, but also handles emoji and rare symbols, which are too big to fit in one UTF-16 code.
There are three methods for copying out part of a string:
slice() copies from start up to, but not including, end:
let text = "Apple, Banana, Kiwi";
// 0123456789...
console.log(text.slice(7, 13)); // positions 7 to 12
console.log(text.slice(7)); // leave out end: to the end
console.log(text.slice(-12)); // negative: counted from the end
console.log(text.slice(-12, -6));
end - start is exactly the length you get: slice(7, 13) gives 6 characters. And two slices can meet without overlapping: slice(0, 5) and slice(5) together make the whole string.
substring() is like slice(), except that negative values are treated as 0:
console.log(text.substring(7, 13)); // same as slice here
console.log(text.substring(-3, 5)); // -3 becomes 0
substr() takes a length as its second value instead of an end position: text.substr(7, 6) gives "Banana". It is deprecated, meaning officially discouraged and kept only so old websites keep working. You will see it in older code; use slice() in yours.
Users often type extra spaces before or after their input. trim() removes whitespace (spaces, tabs, line breaks) from both ends; trimStart() and trimEnd() (ES2019) do one side only:
let input = " alex@example.com ";
console.log("[" + input.trim() + "]");
console.log("[" + input.trimStart() + "]");
console.log("[" + input.trimEnd() + "]");
(The square brackets are only there to make the spaces visible.) Spaces in the middle are never touched.
padStart() and padEnd() (ES2017) add characters until the string reaches a target length. Great for times, IDs, and lining up columns:
console.log("5".padStart(4, "0"));
console.log("5".padStart(4, "x"));
console.log("Name".padEnd(8, ".") + "Alex");
padStart is a string method, so it does not exist on numbers. let n = 5; n.padStart(4, "0") throws TypeError: n.padStart is not a function. Convert first: n.toString().padStart(4, "0") or String(n).padStart(4, "0").
let s = "cat and cat";
console.log(s.replace("cat", "dog")); // first match only
console.log(s.replaceAll("cat", "dog")); // every match
replace() only changes the first match, and it is case sensitive: "Cat".replace("cat", "dog") changes nothing.
concat() joins strings, exactly like +. These two lines do the same thing:
let text1 = "Hello" + " " + "World!";
let text2 = "Hello".concat(" ", "World!");
Most programmers just use + or a template string. repeat() repeats a string a given number of times:
console.log("ha".repeat(3));
console.log("-".repeat(20));
split() breaks a string into an array of pieces, cutting wherever a separator appears:
console.log("red,green,blue".split(","));
console.log("one two three".split(" "));
console.log("Hello".split("")); // "" splits every character
console.log("a,b".split()); // no separator: one piece
Splitting a sentence on spaces and checking the array's length is a quick word count. The reverse, turning an array back into a string, is the array method join, covered in the Arrays section.
Emoji and some rare characters are stored as two UTF-16 codes, called a surrogate pair. (That is why "😀".length is 2.) If a string ends up with half a pair, called a lone surrogate, it is broken. isWellFormed() checks for that, and toWellFormed() replaces any lone halves with the "unknown character" symbol �:
console.log("Hello world!".isWellFormed());
console.log("Hello World \uD800".isWellFormed());
You will rarely need these, mostly when sending text to servers. \uD800 is a Unicode escape: \u followed by a character's code in hexadecimal.
Because each method returns a string, you can call the next method straight on the result, reading left to right:
let raw = " hello world ";
let clean = raw.trim().toUpperCase().replaceAll(" ", "_");
console.log(clean);
| length | Number of characters (a property: no parentheses) |
| toUpperCase / toLowerCase | Change case |
| at(i) / charAt(i) / [i] | One character (only at allows negatives) |
| charCodeAt / codePointAt | A character's code number |
| slice / substring | Copy part of a string (substr is deprecated) |
| trim / trimStart / trimEnd | Remove outer whitespace |
| padStart / padEnd | Fill to a length |
| replace / replaceAll | Swap text (first match / all matches) |
| concat / repeat | Build longer strings |
| split(sep) | Break into an array |
| isWellFormed / toWellFormed | Check / fix broken Unicode |
What does this log?
let word = "hello";
word.toUpperCase();
console.log(word);
hello. The uppercase copy was made and thrown away. Strings are immutable, so word is unchanged. Write word = word.toUpperCase(); to keep it.What is "JavaScript".slice(4, 7)?
"Scri""Scr""aScr""Script"S, and slice stops before index 7, so you get positions 4, 5, 6: "Scr". Length is 7 - 4 = 3.You want to show a score of 7 as "007". Which works?
(7).padStart(3, "0")String(7).padStart(3, "0")"7".padEnd(3, "0")"7".repeat(3)padStart). C gives "700". D gives "777"." jOrDaN " into "Jordan" (hint: trim, then slice the first letter and the rest, and fix the case of each)"my file name.txt" into "my-file-name.txt"