String Basics
Strings are immutable sequences of characters. All string methods return new strings and never modify the original.
// 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.
// 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.
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.
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"
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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.Collatorwhen sorting large arrays - Consider
normalize()before comparing strings with accents - Use
[...str]orfor...ofto correctly count/iterate characters - Use
Intl.Segmenterfor 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()