Skip to main content
JavaScript beginner Lesson 8 of 24

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, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;");

  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: &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</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

Frequently Asked Questions

Are JavaScript strings mutable?
No. Strings are immutable — every method that appears to modify a string actually returns a new string. The original is never changed.
What is the difference between slice and substring?
Both extract a portion of a string, but slice accepts negative indices (counting from the end), while substring treats negative values as 0. Prefer slice in most cases.
When should I use a template literal instead of string concatenation?
Almost always. Template literals are more readable, support multi-line strings naturally, and let you embed any expression without breaking the string apart.