JavaScript Strings

5 minute read

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; localeCompare for sorting/i18n
  • ✅ Use .trim() on any string that came from a form, URL, or external source
  • ✅ Use Number(x) to convert; parseInt/parseFloat only 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.length for 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.

← Previous: JS Assignment Next: JS String Methods →

Updated: