String Methods

40 min read Intermediate

Learn to manipulate, search, and transform text with JavaScript's powerful string methods and template literals.

String Basics

Strings are immutable sequences of characters. All string methods return new strings and never modify the original.

JavaScript
// Creating strings
const single = 'Hello';
const double = "World";
const backtick = `Hello World`;

// String length
console.log("Hello".length); // 5

// Accessing characters
const str = "JavaScript";
console.log(str[0]);        // "J"
console.log(str.charAt(0)); // "J"
console.log(str.at(-1));    // "t" (supports negative index)

// Strings are immutable
let text = "Hello";
text[0] = "J";  // Doesn't work!
console.log(text); // "Hello" (unchanged)

// Must create new string
text = "J" + text.slice(1);
console.log(text); // "Jello"

// Comparing strings
console.log("a" < "b");     // true
console.log("Z" < "a");     // true (uppercase before lowercase)
console.log("10" < "9");    // true (string comparison, not numeric!)
console.log("abc" === "abc"); // true

Template Literals

Template literals (backtick strings) offer powerful features for string creation, including interpolation and multi-line strings.

JavaScript
// String interpolation
const name = "Alice";
const age = 25;
const message = `Hello, ${name}! You are ${age} years old.`;
console.log(message); // "Hello, Alice! You are 25 years old."

// Expressions in templates
const a = 10;
const b = 20;
console.log(`Sum: ${a + b}`);        // "Sum: 30"
console.log(`Is adult: ${age >= 18}`); // "Is adult: true"

// Function calls
const upper = (s) => s.toUpperCase();
console.log(`Hello ${upper(name)}`); // "Hello ALICE"

// Multi-line strings
const html = `
    

${name}

Age: ${age}

`; console.log(html); // Nested templates const items = ["Apple", "Banana", "Orange"]; const list = `
    ${items.map(item => `
  • ${item}
  • `).join('\n ')}
`; console.log(list); // Tagged templates (advanced) function highlight(strings, ...values) { return strings.reduce((result, str, i) => { const value = values[i] ? `${values[i]}` : ''; return result + str + value; }, ''); } const highlighted = highlight`Hello ${name}, you are ${age} years old.`; console.log(highlighted); // "Hello Alice, you are 25 years old."

Searching Strings

JavaScript provides several methods to search within strings.

JavaScript
const text = "Hello World, Hello JavaScript!";

// indexOf() - first occurrence (-1 if not found)
console.log(text.indexOf("Hello"));   // 0
console.log(text.indexOf("Hello", 5)); // 13 (start from index 5)
console.log(text.indexOf("Python"));  // -1

// lastIndexOf() - last occurrence
console.log(text.lastIndexOf("Hello")); // 13

// includes() - boolean check
console.log(text.includes("World"));    // true
console.log(text.includes("world"));    // false (case-sensitive)
console.log(text.includes("World", 10)); // false (start from 10)

// startsWith() and endsWith()
console.log(text.startsWith("Hello")); // true
console.log(text.startsWith("World")); // false
console.log(text.endsWith("!"));       // true
console.log(text.endsWith("World", 11)); // true (check first 11 chars)

// search() - returns index (works with regex)
console.log(text.search("World"));     // 6
console.log(text.search(/world/i));    // 6 (case-insensitive regex)

// match() - find matches (works with regex)
const matches = text.match(/Hello/g);
console.log(matches); // ["Hello", "Hello"]

// matchAll() - iterator of all matches with details
const regex = /Hello/g;
for (const match of text.matchAll(regex)) {
    console.log(`Found "${match[0]}" at index ${match.index}`);
}
// Found "Hello" at index 0
// Found "Hello" at index 13

Extracting Substrings

Extract portions of strings using these methods.

JavaScript
const str = "JavaScript";

// slice(start, end) - recommended method
console.log(str.slice(0, 4));   // "Java"
console.log(str.slice(4));      // "Script"
console.log(str.slice(-6));     // "Script" (from end)
console.log(str.slice(-6, -1)); // "Scrip"
console.log(str.slice(4, 0));   // "" (no swap)

// substring(start, end) - similar but no negative indices
console.log(str.substring(0, 4)); // "Java"
console.log(str.substring(4, 0)); // "Java" (swaps if start > end)

// substr(start, length) - deprecated, avoid
console.log(str.substr(4, 3)); // "Scr" (3 characters from index 4)

// Practical examples
const email = "user@example.com";
const username = email.slice(0, email.indexOf("@"));
const domain = email.slice(email.indexOf("@") + 1);
console.log(username); // "user"
console.log(domain);   // "example.com"

// Get file extension
const filename = "document.pdf";
const ext = filename.slice(filename.lastIndexOf(".") + 1);
console.log(ext); // "pdf"

// Extract with split()
const url = "https://example.com/path/to/page";
const parts = url.split("/");
console.log(parts); // ["https:", "", "example.com", "path", "to", "page"]
console.log(parts[2]); // "example.com"
Use slice()

Prefer slice() over substring() or substr(). It has cleaner behavior with negative indices and is consistent with array's slice().

Transforming Strings

Methods for changing the case and content of strings.

JavaScript
// Case conversion
const text = "Hello World";
console.log(text.toUpperCase()); // "HELLO WORLD"
console.log(text.toLowerCase()); // "hello world"

// Capitalize first letter
function capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
console.log(capitalize("jAVASCRIPT")); // "Javascript"

// Title case
function titleCase(str) {
    return str
        .split(' ')
        .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
        .join(' ');
}
console.log(titleCase("hello world")); // "Hello World"

// replace() - replace first occurrence
const msg = "Hello World World";
console.log(msg.replace("World", "JavaScript"));
// "Hello JavaScript World"

// replaceAll() - replace all occurrences
console.log(msg.replaceAll("World", "JS"));
// "Hello JS JS"

// Using regex with replace
console.log(msg.replace(/World/g, "JS")); // Same as replaceAll
console.log("Hello 123 World 456".replace(/\d+/g, "X"));
// "Hello X World X"

// Replace with function
const prices = "Item costs $10 or $20";
const updated = prices.replace(/\$(\d+)/g, (match, num) => {
    return `$${parseInt(num) * 2}`;
});
console.log(updated); // "Item costs $20 or $40"

// Repeat string
console.log("ab".repeat(3));  // "ababab"
console.log("-".repeat(10));  // "----------"

// Concatenation
const s1 = "Hello";
const s2 = "World";
console.log(s1.concat(" ", s2)); // "Hello World"
console.log(s1 + " " + s2);      // "Hello World" (preferred)

Trimming and Padding

Clean up whitespace or add padding to strings.

JavaScript
// Trimming whitespace
const messy = "   Hello World   \n";
console.log(messy.trim());      // "Hello World"
console.log(messy.trimStart()); // "Hello World   \n"
console.log(messy.trimEnd());   // "   Hello World"

// Padding
const num = "5";
console.log(num.padStart(3, "0")); // "005"
console.log(num.padEnd(3, "0"));   // "500"

const price = "42";
console.log(price.padStart(6, " ")); // "    42"
console.log(price.padStart(6, "$")); // "$$$$42"

// Practical: format numbers
function formatNumber(n, digits = 2) {
    return String(n).padStart(digits, "0");
}
console.log(formatNumber(5));    // "05"
console.log(formatNumber(123));  // "123"

// Format time
const hours = 9;
const minutes = 5;
const time = `${formatNumber(hours)}:${formatNumber(minutes)}`;
console.log(time); // "09:05"

// Format currency
function formatPrice(amount) {
    return "$" + amount.toFixed(2).padStart(8, " ");
}
console.log(formatPrice(9.99));   // "$    9.99"
console.log(formatPrice(129.99)); // "$  129.99"

Splitting and Joining

Convert between strings and arrays.

JavaScript
// split() - string to array
const sentence = "Hello World JavaScript";
console.log(sentence.split(" "));  // ["Hello", "World", "JavaScript"]
console.log(sentence.split(""));   // ["H", "e", "l", "l", "o", ...]
console.log(sentence.split());     // ["Hello World JavaScript"]

// Limit results
console.log(sentence.split(" ", 2)); // ["Hello", "World"]

// Split with regex
const data = "apple,banana;orange|grape";
console.log(data.split(/[,;|]/)); // ["apple", "banana", "orange", "grape"]

// join() - array to string
const words = ["Hello", "World"];
console.log(words.join(" "));  // "Hello World"
console.log(words.join("-"));  // "Hello-World"
console.log(words.join(""));   // "HelloWorld"
console.log(words.join());     // "Hello,World" (default: comma)

// Practical examples
// Reverse a string
const str = "JavaScript";
const reversed = str.split("").reverse().join("");
console.log(reversed); // "tpircSavaJ"

// Slug from title
const title = "Hello World: A JavaScript Guide!";
const slug = title
    .toLowerCase()
    .replace(/[^a-z0-9\s-]/g, "")
    .trim()
    .replace(/\s+/g, "-");
console.log(slug); // "hello-world-a-javascript-guide"

// Parse CSV line
const csv = "John,Doe,john@example.com,30";
const [firstName, lastName, email, age] = csv.split(",");
console.log({ firstName, lastName, email, age });

// Build query string
const params = { name: "Alice", age: 25, city: "NYC" };
const queryString = Object.entries(params)
    .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
    .join("&");
console.log(queryString); // "name=Alice&age=25&city=NYC"

Comparing Strings

Methods for comparing and sorting strings properly.

JavaScript
// localeCompare() - locale-aware comparison
console.log("a".localeCompare("b")); // -1 (a comes before b)
console.log("b".localeCompare("a")); // 1 (b comes after a)
console.log("a".localeCompare("a")); // 0 (equal)

// Case-insensitive comparison
const s1 = "Hello";
const s2 = "hello";
console.log(s1 === s2); // false
console.log(s1.toLowerCase() === s2.toLowerCase()); // true
console.log(s1.localeCompare(s2, undefined, { sensitivity: "base" })); // 0

// Sorting strings properly
const words = ["Banana", "apple", "Cherry"];

// Default sort (uppercase first)
console.log([...words].sort());
// ["Banana", "Cherry", "apple"]

// Case-insensitive sort
console.log([...words].sort((a, b) => a.localeCompare(b)));
// ["apple", "Banana", "Cherry"]

// Sorting with numbers
const items = ["item2", "item10", "item1"];
console.log([...items].sort()); // ["item1", "item10", "item2"]

// Natural sort with localeCompare
console.log([...items].sort((a, b) => 
    a.localeCompare(b, undefined, { numeric: true })
)); // ["item1", "item2", "item10"]

// Locale-specific sorting
const german = ["ä", "z", "a"];
console.log([...german].sort((a, b) => a.localeCompare(b, "de")));
// ["a", "ä", "z"] (German sorting)

console.log([...german].sort((a, b) => a.localeCompare(b, "sv")));
// ["a", "z", "ä"] (Swedish sorting - ä comes last)

Unicode and Special Characters

Working with Unicode, emojis, and special characters.

JavaScript
// Character codes
console.log("A".charCodeAt(0)); // 65
console.log(String.fromCharCode(65)); // "A"

// Unicode code points (supports emojis)
console.log("😀".codePointAt(0)); // 128512
console.log(String.fromCodePoint(128512)); // "😀"

// Escape sequences
const escaped = "Hello\tWorld\nNew Line";
console.log(escaped);
// Hello   World
// New Line

// Unicode escape
console.log("\u0041"); // "A"
console.log("\u{1F600}"); // "😀" (ES6 syntax)

// Handling emojis correctly
const emoji = "👨‍👩‍👧‍👦";
console.log(emoji.length); // 11 (wrong!)

// Use spread to handle properly
console.log([...emoji].length); // 7 (grapheme clusters)

// For accurate count, use Intl.Segmenter
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const segments = [...segmenter.segment(emoji)];
console.log(segments.length); // 1 (correct!)

// Normalize Unicode
const e1 = "café"; // 'e' + combining accent
const e2 = "café"; // single 'é' character
console.log(e1 === e2); // might be false!
console.log(e1.normalize() === e2.normalize()); // true

// Check if string contains only ASCII
const isAscii = (str) => /^[\x00-\x7F]*$/.test(str);
console.log(isAscii("Hello")); // true
console.log(isAscii("Héllo")); // false

Practical Examples

Common string manipulation tasks.

JavaScript
// Truncate text with ellipsis
function truncate(str, maxLength) {
    if (str.length <= maxLength) return str;
    return str.slice(0, maxLength - 3) + "...";
}
console.log(truncate("Hello World", 8)); // "Hello..."

// Count words
function wordCount(str) {
    return str.trim().split(/\s+/).filter(Boolean).length;
}
console.log(wordCount("Hello   World  ")); // 2

// Validate email (basic)
function isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
console.log(isValidEmail("test@example.com")); // true

// Mask sensitive data
function maskEmail(email) {
    const [user, domain] = email.split("@");
    const masked = user[0] + "*".repeat(user.length - 2) + user.slice(-1);
    return `${masked}@${domain}`;
}
console.log(maskEmail("john.doe@example.com")); // "j******e@example.com"

// camelCase to kebab-case
function camelToKebab(str) {
    return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
}
console.log(camelToKebab("backgroundColor")); // "background-color"

// kebab-case to camelCase
function kebabToCamel(str) {
    return str.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
}
console.log(kebabToCamel("background-color")); // "backgroundColor"

// Extract initials
function getInitials(name) {
    return name
        .split(" ")
        .map(word => word[0])
        .join("")
        .toUpperCase();
}
console.log(getInitials("John Doe")); // "JD"

// Remove HTML tags
function stripTags(html) {
    return html.replace(/<[^>]*>/g, "");
}
console.log(stripTags("

Hello World

")); // "Hello World" // Highlight search term function highlight(text, term) { const regex = new RegExp(`(${term})`, "gi"); return text.replace(regex, "$1"); } console.log(highlight("Hello World", "world")); // "Hello World"

Tagged Template Literals

Tagged templates allow you to parse template literals with a custom function, enabling powerful string processing patterns.

JavaScript
// Tag function receives strings array and values
function myTag(strings, ...values) {
    console.log("Strings:", strings);
    console.log("Values:", values);
    return strings.reduce((acc, str, i) => 
        acc + str + (values[i] || ""), "");
}

const name = "Alice";
const age = 30;
myTag`Hello ${name}, you are ${age} years old`;
// Strings: ["Hello ", ", you are ", " years old"]
// Values: ["Alice", 30]

// HTML escaping to prevent XSS
function html(strings, ...values) {
    const escape = str => String(str)
        .replace(/&/g, "&")
        .replace(//g, ">")
        .replace(/"/g, """)
        .replace(/'/g, "'");
    
    return strings.reduce((acc, str, i) => 
        acc + str + (i < values.length ? escape(values[i]) : ""), "");
}

const userInput = '';
console.log(html`
${userInput}
`); //
<script>alert("XSS")</script>
// Styled components pattern (CSS-in-JS) function css(strings, ...values) { const combined = strings.reduce((acc, str, i) => acc + str + (values[i] || ""), ""); const style = document.createElement("style"); style.textContent = combined; return style; } const primaryColor = "#007bff"; const styleElement = css` .button { background: ${primaryColor}; padding: 10px 20px; border-radius: 4px; } `; // Internationalization tag const translations = { en: { greeting: "Hello" }, es: { greeting: "Hola" }, fr: { greeting: "Bonjour" } }; function i18n(strings, ...keys) { const locale = navigator.language.slice(0, 2); const dict = translations[locale] || translations.en; return strings.reduce((acc, str, i) => acc + str + (dict[keys[i]] || keys[i] || ""), ""); } console.log(i18n`${"greeting"} World!`); // "Hello World!" or "Hola World!" based on locale // String.raw - access raw strings (escapes are not processed) console.log(`Line1\nLine2`); // Newline is interpreted // Line1 // Line2 console.log(String.raw`Line1\nLine2`); // Raw string // Line1\nLine2 // Useful for regex patterns const path = String.raw`C:\Users\Documents\file.txt`; console.log(path); // C:\Users\Documents\file.txt

Unicode and Internationalization

JavaScript strings are UTF-16 encoded. Understanding Unicode is crucial for international applications.

JavaScript
// Unicode code points and escape sequences
console.log("\u0048\u0065\u006C\u006C\u006F"); // "Hello"
console.log("\u{1F600}");  // "😀" (ES6 extended syntax)

// Problem: Some characters are "surrogate pairs" (2 code units)
const emoji = "😀";
console.log(emoji.length);        // 2 (not 1!)
console.log(emoji.charCodeAt(0)); // 55357 (high surrogate)
console.log(emoji.charCodeAt(1)); // 56832 (low surrogate)

// Solution: Use codePointAt and for...of
console.log(emoji.codePointAt(0)); // 128512 (correct!)
console.log([...emoji].length);    // 1 (correct!)

// Iterating over characters correctly
const text = "Hello 😀 World";
console.log(text.length);     // 14 (includes surrogate pair as 2)
console.log([...text].length); // 13 (correct character count)

// String.fromCodePoint for creating from code points
console.log(String.fromCodePoint(128512)); // "😀"
console.log(String.fromCodePoint(0x1F600)); // "😀"

// Normalize for comparing strings with accents
const str1 = "café";  // 'é' as single character
const str2 = "cafe\u0301"; // 'e' + combining accent

console.log(str1 === str2);                        // false
console.log(str1.normalize() === str2.normalize()); // true

// Normalization forms
console.log("é".normalize("NFD").length); // 2 (decomposed)
console.log("é".normalize("NFC").length); // 1 (composed)

// Counting grapheme clusters (user-perceived characters)
const complex = "👨‍👩‍👧‍👦"; // Family emoji (single grapheme, 7 code points!)
console.log(complex.length);     // 11
console.log([...complex].length); // 7

// Correct counting with Intl.Segmenter (ES2022)
if (Intl.Segmenter) {
    const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
    const segments = [...segmenter.segment(complex)];
    console.log(segments.length); // 1 (correct!)
}

// Working with different scripts
const mixed = "Hello مرحبا 你好";
console.log([...mixed]); // Correctly iterates characters

localeCompare and Collation

For proper string comparison across languages, use localeCompare instead of direct comparison.

JavaScript
// Basic localeCompare
console.log("apple".localeCompare("banana")); // -1 (apple comes first)
console.log("banana".localeCompare("apple")); // 1 (banana comes after)
console.log("apple".localeCompare("apple"));  // 0 (equal)

// Case-insensitive comparison
console.log("Apple".localeCompare("apple", undefined, { 
    sensitivity: "base" 
})); // 0 (treated as equal)

// Sorting with localeCompare
const fruits = ["Banana", "apple", "Cherry"];

// Wrong: ASCII comparison
console.log(fruits.sort()); 
// ["Banana", "Cherry", "apple"] - capitals first!

// Correct: locale-aware
console.log(fruits.sort((a, b) => a.localeCompare(b)));
// ["apple", "Banana", "Cherry"]

// Locale-specific sorting
const germanWords = ["Öl", "Apfel", "Übung"];
console.log(germanWords.sort((a, b) => a.localeCompare(b, "de")));
// ["Apfel", "Öl", "Übung"] - German alphabet order

// Numeric sorting
const files = ["file1", "file10", "file2", "file20"];
console.log(files.sort());
// ["file1", "file10", "file2", "file20"] - wrong!

console.log(files.sort((a, b) => 
    a.localeCompare(b, undefined, { numeric: true })
));
// ["file1", "file2", "file10", "file20"] - correct!

// All localeCompare options
const options = {
    sensitivity: "base",   // "base", "accent", "case", "variant"
    ignorePunctuation: true,
    numeric: true,
    caseFirst: "upper"     // "upper", "lower", "false"
};

// Using Intl.Collator for repeated comparisons (faster)
const collator = new Intl.Collator("en", { numeric: true });
const sortedFiles = files.sort(collator.compare);

// Performance: Collator is much faster for large arrays
console.time("localeCompare");
largeArray.sort((a, b) => a.localeCompare(b));
console.timeEnd("localeCompare");

console.time("Collator");
const fastCollator = new Intl.Collator();
largeArray.sort(fastCollator.compare);
console.timeEnd("Collator");  // 2-10x faster!

// Search with diacritics ignored
function fuzzySearch(needle, haystack) {
    const options = { sensitivity: "base" };
    return haystack.filter(item => 
        item.localeCompare(needle, undefined, options) === 0 ||
        item.toLowerCase().includes(needle.toLowerCase())
    );
}

console.log(fuzzySearch("cafe", ["café", "Cafe", "coffee"]));
// ["café", "Cafe"]

Internationalization Best Practices

  • Always use localeCompare() for sorting user-visible strings
  • Use Intl.Collator when sorting large arrays
  • Consider normalize() before comparing strings with accents
  • Use [...str] or for...of to correctly count/iterate characters
  • Use Intl.Segmenter for grapheme cluster counting

Summary

Template Literals

Use backticks for interpolation: `Hello ${name}`

Searching

includes(), startsWith(), indexOf()

Extracting

Use slice(start, end) for substrings

Transforming

replace(), toUpperCase(), toLowerCase()

Split/Join

split() to array, join() to string

Trim/Pad

trim(), padStart(), padEnd()