JavaScript Strings
A string is JavaScript’s text type — anything from a single character to the entire content of a book. Strings are immutable: every method that looks like it modifies a string actually returns a new one. Modern JavaScript also gives you template literals, which solve the awkward "Hello, " + name + "!" concatenation and unlock multi-line strings with interpolation.
Three Ways to Quote
const a = "double quotes";
const b = 'single quotes';
const c = `template literal`;
All three create plain strings. Double quotes are the modern default (Prettier, Airbnb, StandardJS all agree). Template literals (backticks) unlock interpolation and multi-line.
| Quoting | Interpolation | Multi-line | Common Use |
|---|---|---|---|
"..." |
❌ | ❌ | Default for plain strings |
'...' |
❌ | ❌ | Strings containing " |
`...` |
✅ ${ } |
✅ | Strings with variables or newlines |
const quote = 'She said "hi"'; // single quotes avoid escaping "
const apos = "Don't worry"; // double quotes avoid escaping '
const both = "She said \"don't\""; // last resort: escape with \
Template Literals
Backticks (`) enable two features "..." can’t:
1. Interpolation
${ expression } evaluates inside the string:
const name = "Ada";
const age = 36;
const greet = `Hi ${name}, you are ${age} years old.`;
console.log(greet); // Hi Ada, you are 36 years old.
// Any expression works
const greet2 = `Next year you'll be ${age + 1}`;
const greet3 = `Name uppercase: ${name.toUpperCase()}`;
Old-style concatenation, for comparison:
// ❌ Verbose
"Hi " + name + ", you are " + age + " years old.";
// ✅ Clean
`Hi ${name}, you are ${age} years old.`;
2. Multi-line strings
Backticks preserve newlines literally:
const poem = `Roses are red,
violets are blue,
JavaScript is weird,
but mostly true.`;
No \n needed. Indentation is preserved — be careful if your template is indented inside a function.
3. Tagged templates (advanced)
A function call attached to a template literal. Used by libraries like styled-components and SQL builders.
function highlight(strings, ...values) {
return strings.reduce((out, str, i) =>
out + str + (values[i] !== undefined ? `[${values[i]}]` : ""), "");
}
const result = highlight`Hi ${"Ada"}, age ${36}`;
// "Hi [Ada], age [36]"
String Length
Every string has a .length property — the number of UTF-16 code units (not always the number of “visible characters” for emoji and combining marks).
"hello".length // 5
"".length // 0
"😀".length // 2 ← single emoji uses two code units
Use Array.from(str).length for the actual character count when emoji matter:
Array.from("😀").length // 1
Escape Sequences
A backslash \ introduces a special character:
| Sequence | Meaning |
|---|---|
\n |
Newline |
\t |
Tab |
\r |
Carriage return |
\\ |
Literal backslash |
\" |
Literal " (inside "...") |
\' |
Literal ' (inside '...') |
\` |
Literal backtick (inside `...`) |
\b |
Backspace |
\f |
Form feed |
\u00E9 |
Unicode code point (e.g. é) |
\u{1F600} |
Extended Unicode (e.g. 😀) |
console.log("a\nb\nc");
// a
// b
// c
console.log("Path: C:\\Users\\Ada");
// Path: C:\Users\Ada
console.log("\u00E9 = é");
// é = é
Strings Are Immutable
You can’t change a character in place. Every “modifying” method returns a new string.
let s = "hello";
s[0] = "H"; // ❌ silently ignored
console.log(s); // "hello"
s = "H" + s.slice(1);
console.log(s); // "Hello"
const upper = "abc".toUpperCase();
console.log(upper); // "ABC"
console.log("abc"); // "abc" — original unchanged
That’s why operations like replace, slice, concat, toUpperCase return values you must capture.
Comparing Strings
=== compares character by character:
"abc" === "abc" // true
"abc" === "abd" // false
"abc" < "abd" // true ← lexicographic comparison
"ABC" < "abc" // true ← uppercase letters come first in ASCII
For locale-aware comparison (correct handling of accents, case), use localeCompare:
"ä".localeCompare("z", "de"); // -1 ← ä before z in German
"ä".localeCompare("z", "sv"); // 1 ← ä after z in Swedish
For case-insensitive equality, normalise first:
"Hello".toLowerCase() === "hello".toLowerCase(); // true
Accessing Characters
const s = "hello";
s[0] // "h" ← bracket notation
s.charAt(0) // "h" ← method form
s.at(-1) // "o" ← supports negative indexes (ES2022)
s.charCodeAt(0) // 104 ← UTF-16 code unit
s.codePointAt(0) // 104 ← Unicode code point (handles emoji)
| Access Method | Out-of-range | Supports negative |
|---|---|---|
s[i] |
undefined |
❌ |
s.charAt(i) |
"" (empty string) |
❌ |
s.at(i) |
undefined |
✅ s.at(-1) |
Concatenation
"hello" + " " + "world" // "hello world"
"hello".concat(" ", "world") // "hello world" — method form
`hello world` // template literal (cleanest)
// += is the most common in loops
let s = "";
for (const x of [1, 2, 3]) {
s += x + " ";
}
console.log(s); // "1 2 3 "
For many strings, array + join is faster and cleaner than chained +:
const parts = ["GET", "/users", "HTTP/1.1"];
parts.join(" "); // "GET /users HTTP/1.1"
A Handful of Everyday Methods
(Full reference is chapter 15.)
const s = " Hello, World! ";
s.length // 17
s.trim() // "Hello, World!"
s.toLowerCase() // " hello, world! "
s.toUpperCase() // " HELLO, WORLD! "
s.includes("World") // true
s.indexOf("World") // 9
s.replace("World", "JS") // " Hello, JS! "
s.split(",") // [" Hello", " World! "]
s.slice(2, 7) // "Hello"
s.repeat(2) // " Hello, World! Hello, World! "
"42".padStart(5, "0") // "00042"
"42".padEnd(5, ".") // "42..."
"abc".startsWith("a") // true
"abc".endsWith("c") // true
Converting To/From Strings
// To string
String(42) // "42"
(42).toString() // "42"
`${42}` // "42" — interpolation coerces
42 + "" // "42" — works but ugly
// From string
Number("42") // 42
parseInt("42px") // 42 — reads leading digits
parseFloat("3.14abc") // 3.14
+("42") // 42 — unary +
Strings Are Sequences
You can iterate them with for…of:
for (const char of "abc") {
console.log(char);
}
// a
// b
// c
[..."abc"] // ["a", "b", "c"]
Array.from("abc") // ["a", "b", "c"]
for…of correctly handles surrogate pairs (emoji), unlike a classic for loop with s[i].
A Realistic Example
function formatGreeting(user) {
const name = (user?.name ?? "guest").trim();
const initial = name[0]?.toUpperCase() ?? "?";
const today = new Date().toLocaleDateString("en-US", {
weekday: "long",
});
return `Hi ${initial}. ${name.slice(1)} — happy ${today}!`;
}
console.log(formatGreeting({ name: "ada" }));
// "Hi A. da — happy Tuesday!"
Template literals, optional chaining, trim, slice, toUpperCase — all the everyday tools in one realistic function.
Best Practices Checklist
- ✅ Use template literals for any string with a variable or newline
- ✅ Use
===to compare strings;localeComparefor sorting/i18n - ✅ Use
.trim()on any string that came from a form, URL, or external source - ✅ Use
Number(x)to convert;parseInt/parseFloatonly when extracting numbers from mixed text - ✅ For many concatenations, build an array and
.join() - ❌ Don’t mutate
s[i] = "X"— it silently fails - ❌ Don’t trust
s.lengthfor emoji-correct character count - ❌ Don’t use
new String("x")— it creates a string object (weird), not a primitive
What’s Next?
You can read, build, and compare strings. Now let’s go deep on the full set of string methods — the ones you’ll reach for daily and the modern additions you might not know about.