Chapter 11: Strings and Text
Strings are the primary way JavaScript represents text. Whether you are displaying messages, manipulating user input, or constructing templates for output, strings are essential to almost every program. They are sequences of Unicode characters enclosed in quotes and can be created with single, double, or backtick marks. While simple in concept, JavaScript strings have many subtleties that arise from their immutability and Unicode handling.
Because strings cannot be changed once created, any operation that seems to alter a string actually produces a new one. This behaviour keeps data consistent and predictable, but it also means that performance can become a consideration in large-scale text processing.
JavaScript also provides template literals, a modern syntax using backticks that allows embedded expressions, multiline text, and improved readability for code that generates dynamic content. Template interpolation greatly simplifies string composition compared to manual concatenation with +.
Since JavaScript is Unicode-based, strings can contain any script or symbol, from emojis to accented characters. However, because some characters are represented by multiple code units, working with text at the character level can be less straightforward than it first appears.
This chapter explores how strings are represented and manipulated in JavaScript. It begins with the basics of string creation and immutability, then moves to template literals and interpolation, followed by an overview of Unicode concepts. It continues with common methods for searching, slicing, and transforming text, and concludes with an introduction to regular expressions, the powerful pattern-matching system built into the language.
String basics and immutability
Strings in JavaScript are sequences of characters enclosed by single quotes ('), double quotes ("), or backticks (`). Each approach creates a String object or literal, although backticks are special and enable template features that will be discussed later. Strings are a primitive type, meaning they are immutable and stored by value rather than by reference.
// Using single and double quotes
let name = 'Robin';
let greeting = "Hello, world!";
// Using backticks (template literal)
let message = `Welcome, ${name}!`;
When a string is said to be immutable, it means its content cannot be altered once created. Any operation that appears to modify a string actually produces a new one. For example, concatenating or replacing parts of a string returns a new value instead of updating the original.
let text = 'Java';
text += 'Script'; // Creates a new string, does not modify 'Java'
console.log(text); // JavaScript
text[0] = 'Y', will silently fail. JavaScript strings are not arrays and cannot be mutated this way.
You can access characters in a string using bracket notation or the charAt() method. Both return the character at the given position, starting at index 0.
let lang = 'JavaScript';
console.log(lang[0]); // J
console.log(lang.charAt(1)); // a
Strings have a length property, which counts the number of UTF-16 code units in the string. For simple text this matches the character count, but for some Unicode characters (such as emojis or accented letters) the result may differ.
console.log('Hello'.length); // 5
console.log('💡'.length); // 2
for...of or Array.from(), both of which understand surrogate pairs.
Understanding that strings are immutable is crucial to writing efficient and predictable text-processing code. Each time you use concatenation, slicing, or replacement, you are creating a new string object rather than modifying the original one. This becomes especially important when manipulating large volumes of text or within tight loops.
Templates and interpolation
Template literals were introduced in ECMAScript 2015 to make working with strings more expressive and less error-prone. They use backticks (`) instead of single or double quotes and can span multiple lines without the need for escape characters. This makes them ideal for constructing readable text blocks or dynamic strings that include variable data.
let name = 'Robin';
let language = 'JavaScript';
let message = `Hello ${name},
welcome to ${language}!`;
console.log(message);
// Output:
// Hello Robin,
// welcome to JavaScript!
The key feature of template literals is interpolation: the ability to embed expressions directly inside a string using the ${...} syntax. Anything between the braces is evaluated as JavaScript and then converted to a string.
let a = 5;
let b = 7;
console.log(`The total is ${a + b}`); // The total is 12
${...} placeholder…even function calls or ternary expressions.
Because template literals support multiline text, they are often used for creating formatted output or inline HTML fragments without cumbersome concatenation operators.
let html = `
<div class="user">
<h2>${name}</h2>
<p>Learning ${language}</p>
</div>`;
Traditional strings require escaping line breaks with \n or manual concatenation with +. Template literals remove that overhead, keeping your code clear and easier to maintain.
In addition to interpolation, template literals can also use tagged templates, a more advanced feature where a function processes the literal’s content and expressions before producing output. This mechanism enables custom formatting or filtering but will be discussed in more detail in later chapters.
Unicode, code points, and iteration
JavaScript strings are based on the Unicode standard, which assigns a unique number, or code point, to every character in virtually every writing system. Internally, JavaScript stores these strings using UTF-16 encoding, where most common characters use a single 16-bit unit, but some (such as emojis or certain symbols) require a pair of units known as a surrogate pair.
let smile = '😊';
console.log(smile.length); // 2 (two UTF-16 code units)
console.log(smile.charCodeAt(0)); // 55357 (first half of surrogate pair)
console.log(smile.charCodeAt(1)); // 56842 (second half)
This dual-unit representation means that some string operations, like indexing or measuring length, can produce confusing results with characters outside the Basic Multilingual Plane (BMP). For instance, slicing or reversing such strings without care can break them into invalid halves.
substring() or split('') can separate surrogate pairs incorrectly.
To handle Unicode accurately, JavaScript provides modern features for working with full code points. The codePointAt() and fromCodePoint() methods correctly interpret characters regardless of how many code units they use.
let heart = '💖';
console.log(heart.codePointAt(0)); // 128150
console.log(String.fromCodePoint(128150)); // 💖
Iteration over strings has also improved with the introduction of for...of loops, which automatically respect Unicode boundaries. This ensures each visible symbol is treated as a single item, even if it uses surrogate pairs internally.
for (let ch of 'A💡B') {
console.log(ch);
}
// Output:
// A
// 💡
// B
for...of loop and Array.from() both iterate over entire Unicode characters, making them safer for text processing than traditional index-based loops.
JavaScript also exposes the normalize() method, which helps standardize characters that can be represented in multiple ways (for example, accented letters). Unicode normalization ensures that two visually identical strings will compare equally, even if their internal byte sequences differ.
let a = 'é'; // single code point
let b = 'e\u0301'; // 'e' + combining accent
console.log(a === b); // false
console.log(a.normalize() === b.normalize()); // true
Understanding code points, surrogate pairs, and normalization is essential when working with multilingual or symbol-rich text. These features ensure that string manipulation in JavaScript remains consistent across the full range of Unicode characters.
Common string methods
JavaScript’s String object provides a wide range of methods for inspecting, transforming, and extracting parts of text. Since strings are immutable, these methods always return new strings or values rather than altering the original content.
Some of the most frequently used methods deal with case conversion and trimming whitespace:
let text = ' JavaScript ';
console.log(text.toUpperCase()); // JAVASCRIPT
console.log(text.toLowerCase()); // javascript
console.log(text.trim()); // "JavaScript"
To locate substrings, use indexOf(), lastIndexOf(), or includes(). These help find or check for specific sequences within a string.
let lang = 'JavaScript';
console.log(lang.indexOf('a')); // 1
console.log(lang.lastIndexOf('a')); // 3
console.log(lang.includes('Script')); // true
When you need to extract sections of text, the most common tools are slice(), substring(), and substr() (the last of which is now deprecated but still widely seen). slice() is generally preferred for its clarity and flexibility.
let word = 'JavaScript';
console.log(word.slice(0, 4)); // Java
console.log(word.slice(-6)); // Script
slice() and substring() return a new string. The original remains unchanged, reinforcing the concept of immutability.
String splitting and joining are essential for text processing. split() divides a string into an array, while join() (used on arrays) reconstructs text from elements.
let csv = 'red,green,blue';
let colors = csv.split(',');
console.log(colors); // ['red', 'green', 'blue']
console.log(colors.join(' | ')); // red | green | blue
Replacing or transforming parts of a string can be done with replace() or replaceAll(). Both return a new string, leaving the original untouched.
let phrase = 'I like cats. Cats are cute.';
console.log(phrase.replace('cats', 'dogs'));
// I like dogs. Cats are cute.
console.log(phrase.replaceAll('Cats', 'Dogs'));
// I like cats. Dogs are cute.
replace() method changes only the first match unless a regular expression with the global flag (/g) is used, or you switch to replaceAll().
Finally, there are several convenient methods for checking prefixes, suffixes, or matching patterns:
let file = 'index.html';
console.log(file.startsWith('index')); // true
console.log(file.endsWith('.html')); // true
console.log(file.match(/\.html$/)); // ['.html']
Together, these methods cover most everyday string operations: finding, slicing, replacing, trimming, matching, and joining. They form the backbone of text handling in JavaScript and are used in nearly every kind of program that processes human-readable data.
Regular expressions
Regular expressions (often called regex or regexp) are patterns used to search, match, and manipulate text. In JavaScript, they are represented by the RegExp object and can be created either with literal syntax, using slashes (/pattern/), or by calling the constructor new RegExp().
// Two equivalent forms
let re1 = /cat/;
let re2 = new RegExp('cat');
Regular expressions can be used with several string methods, including match(), search(), replace(), and split(). When applied, they allow sophisticated text processing far beyond simple substring checks.
let text = 'The cat sat on the mat.';
console.log(text.match(/cat/)); // ['cat']
console.log(text.search(/sat/)); // 8
console.log(text.replace(/mat/, 'rug')); // The cat sat on the rug.
Regex patterns can include special characters known as metacharacters that define how text should be matched. For example, . matches any single character, \d matches digits, and \s matches whitespace. Quantifiers like +, *, and {n,m} specify repetition.
let sample = 'ID: A12345';
console.log(sample.match(/A\d+/)); // ['A12345']
g flag makes the regex global, allowing multiple matches, while the i flag makes it case-insensitive. You can combine them as /pattern/gi.
Grouping and alternation provide further control. Parentheses (...) group parts of a pattern, and the pipe | offers a choice between alternatives.
let str = 'apple or orange';
console.log(str.match(/apple|orange/)); // ['apple']
Regex captures matched groups, which can be accessed by number or by name (with modern syntax). This is useful for extracting parts of structured text, such as dates or tags.
let date = '2025-10-28';
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
let match = date.match(re);
console.log(match.groups.year); // 2025
Regular expressions can also be used directly through the test() method of a RegExp object, which simply returns true or false depending on whether the pattern matches.
let valid = /^\d{4}-\d{2}-\d{2}$/; // yyyy-mm-dd
console.log(valid.test('2025-10-28')); // true
console.log(valid.test('28/10/2025')); // false
Regular expressions are an indispensable tool for validating, parsing, and transforming text. They can appear cryptic at first, but even a basic grasp of their syntax greatly expands what you can do with strings in JavaScript. Later chapters will revisit regex in more advanced contexts, including data extraction and text validation in real-world applications.