Working with Strings in JavaScript
Learn how to create, manipulate, and transform strings in JavaScript using template literals, built-in methods, and regular expressions.
Strings are one of the most-used types in JavaScript. They carry user input, API responses, labels, URLs, and HTML fragments — practically every program works with them extensively. Beyond the basics, the built-in String methods cover the vast majority of real-world text manipulation without needing a library.
Creating Strings
JavaScript gives you three quoting styles for string literals. Single and double quotes behave identically — pick one and stay consistent within a project. Backticks (template literals) are more powerful and are generally preferred for anything that involves variables or spans multiple lines.
// Three equivalent ways to create a string literal
const single = 'Hello, world';
const double = "Hello, world";
const template = `Hello, world`;
// Escape characters work in all three
const path = 'C:\\Users\\alice\\docs';
const newline = "line one\nline two";
const tab = "col1\tcol2";
Single and double quotes are interchangeable — pick one and stay consistent within a project.
Template Literals
Template literals unlock two things that plain quotes can’t do: expression interpolation with ${} and natural multi-line strings. Any valid JavaScript expression can go inside ${} — variables, ternaries, method calls, arithmetic. This eliminates the fragile string concatenation that used to break across many + operators.
const name = "Alice";
const score = 94.5;
// Embed any expression with ${}
console.log(`Hello, ${name}! Your score is ${score.toFixed(1)}.`);
// "Hello, Alice! Your score is 94.5."
// Ternary inside template
const status = `Status: ${score >= 60 ? "pass" : "fail"}`;
// Function call inside template
const slug = `post/${name.toLowerCase().replace(/\s+/g, "-")}`;
Multi-line strings
Template literals preserve newlines exactly as written, making HTML snippets and multi-line messages straightforward. The old approach required explicit \n escape sequences inside concatenated strings — error-prone and hard to read.
// Template literal preserves newlines naturally
const html = `
<article>
<h2>${name}</h2>
<p>Score: ${score}</p>
</article>
`.trim();
// Old way — ugly and error-prone
const oldHtml = "<article>\n <h2>" + name + "</h2>\n</article>";
Nested template literals
const items = ["apples", "oranges", "bananas"];
const list = `
<ul>
${items.map(item => `<li>${item}</li>`).join("\n ")}
</ul>
`.trim();
Key String Methods
Searching
These methods let you check whether a string contains a substring, where it starts or ends, and at which position a match occurs. They’re the first tools to reach for when validating input or filtering content.
const sentence = "The quick brown fox jumps over the lazy dog";
sentence.includes("fox"); // true
sentence.startsWith("The"); // true
sentence.endsWith("dog"); // true
sentence.indexOf("fox"); // 16 (first occurrence, or -1 if not found)
sentence.lastIndexOf("the"); // 31 (last occurrence, case-sensitive)
// Case-insensitive check — normalize both sides first
sentence.toLowerCase().includes("FOX".toLowerCase()); // true
Extracting
slice is the go-to method for pulling out a portion of a string. It accepts negative indices that count backwards from the end, which is far more convenient than computing str.length - n yourself. The newer at() method applies the same negative-index convenience to single character access.
const str = "JavaScript";
str.slice(0, 4); // "Java" — from index 0 up to (not including) 4
str.slice(4); // "Script" — from index 4 to the end
str.slice(-6); // "Script" — negative counts from the end
str.slice(4, -2); // "Scri" — combine positive start and negative end
str.at(0); // "J" — cleaner than str[0]
str.at(-1); // "t" — last character, no length math needed
Transforming
String transformation methods cover the most common text operations: trimming whitespace, changing case, padding, splitting, and repeating. All of them return a new string — none modify the original.
const raw = " Hello, World! ";
raw.trim(); // "Hello, World!"
raw.trimStart(); // "Hello, World! "
raw.trimEnd(); // " Hello, World!"
"hello".toUpperCase(); // "HELLO"
"WORLD".toLowerCase(); // "world"
"abc".repeat(3); // "abcabcabc"
"5".padStart(4, "0"); // "0005" — useful for zero-padded IDs
"42".padEnd(6, "."); // "42...."
"one,two,three".split(","); // ["one", "two", "three"]
"one,two,three".split(",", 2); // ["one", "two"] — limit stops after 2 parts
Replacing
replace and replaceAll let you substitute parts of a string. When you pass a string as the search pattern, replace only replaces the first match — a common surprise. Use replaceAll or a global regex (/pattern/g) when you need to replace every occurrence.
const text = "I like cats. Cats are great.";
// replace — only first match
text.replace("Cats", "Dogs"); // "I like cats. Dogs are great."
// replaceAll — every match (exact string, case-sensitive)
text.replaceAll("cats", "dogs"); // "I like dogs. Cats are great."
// Replace with regex for case-insensitive, all occurrences
text.replace(/cats/gi, "dogs"); // "I like dogs. dogs are great."
// Replace with a function — transform each match
"hello world".replace(/\b\w/g, c => c.toUpperCase()); // "Hello World"
Regular Expression Basics
Regular expressions describe patterns in text. They look intimidating at first, but a handful of common patterns covers the vast majority of real-world use cases — email validation, phone number extraction, date parsing, and more. You don’t need to memorise all of regex — knowing the building blocks lets you look up what you need.
const email = "[email protected]";
// Test whether a string matches a pattern
/^[\w.+-]+@[\w-]+\.[a-z]{2,}$/i.test(email); // true
// Extract all matches — the `g` flag means "global" (find all, not just first)
const text = "Call us at 555-1234 or 555-5678";
const phones = text.match(/\d{3}-\d{4}/g);
// ["555-1234", "555-5678"]
// Capture groups — extract specific parts of a match
const date = "2024-07-04";
const [, year, month, day] = date.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(year, month, day); // "2024" "07" "04"
// Named capture groups — more readable than positional
const { groups } = "2024-07-04".match(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/);
console.log(groups.y, groups.m, groups.d); // "2024" "07" "04"
Tagged Template Literals
A tagged template passes the literal string parts and interpolated values to a function before the string is assembled. This gives you a hook to transform, escape, or validate the values being embedded. The most important real-world use case is preventing XSS when building HTML from user input — you can escape dangerous characters before they become part of the string.
// Escape HTML to prevent XSS injection
function html(strings, ...values) {
const escape = str =>
String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
return strings.reduce((result, part, i) => {
return result + part + (values[i] !== undefined ? escape(values[i]) : "");
}, "");
}
const userInput = '<script>alert("xss")</script>';
const safe = html`<p>User said: ${userInput}</p>`;
// <p>User said: <script>alert("xss")</script></p>
Real-World Patterns
Slug generation
Converting arbitrary text to a URL-safe slug is a common requirement for blogs and CMS systems. The key steps are lowercasing, stripping special characters, replacing whitespace with hyphens, and collapsing repeated hyphens.
function slugify(text) {
return text
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, "") // remove non-word chars (keep letters, digits, spaces, hyphens)
.replace(/\s+/g, "-") // replace spaces with hyphens
.replace(/-+/g, "-"); // collapse multiple consecutive hyphens
}
slugify(" Hello, World! -- A New Post ");
// "hello-world-a-new-post"
Truncate with ellipsis
function truncate(str, maxLength) {
if (str.length <= maxLength) return str;
return str.slice(0, maxLength - 3).trimEnd() + "...";
}
truncate("The quick brown fox jumps", 20); // "The quick brown f..."
Parse a query string
// URLSearchParams handles encoding, repeated keys, and edge cases for you
function parseQuery(search) {
return Object.fromEntries(new URLSearchParams(search));
}
parseQuery("?page=2&sort=asc&filter=active");
// { page: "2", sort: "asc", filter: "active" }
Capitalise each word
// \b matches a word boundary, \w matches the first character after it
const titleCase = str =>
str.replace(/\b\w/g, c => c.toUpperCase());
titleCase("the dark knight rises"); // "The Dark Knight Rises"
Common Pitfalls
String comparison is case-sensitive:
"apple" === "Apple"; // false
// Always normalise to the same case before comparing
"apple" === "Apple".toLowerCase(); // true
split on an empty string gives individual characters, not an empty array:
"abc".split(""); // ["a", "b", "c"] — splits on every character boundary
"abc".split(); // ["abc"] — no separator wraps the whole string in an array
Checking type safely — typeof is reliable for string primitives, but avoid new String():
// Checking for a string primitive
typeof value === "string" // true for "hello", false for everything else
// new String() creates a String object, not a primitive — avoid it
typeof new String("hello") // "object" — not "string"
new String("hello") instanceof String // true — but causes confusing comparisons