JavaScript Essentials

Strings & String Methods

45 min Lesson 5 of 60

Introduction to Strings in JavaScript

Strings are one of the most fundamental data types in JavaScript. A string is a sequence of characters used to represent text. Whether you are displaying a user's name, building a URL, parsing data from an API, or formatting output for a web page, strings are everywhere in JavaScript programming. In this lesson, you will learn how to create strings, understand their immutable nature, and master the many built-in methods JavaScript provides for manipulating text.

Creating Strings

JavaScript offers three ways to create string literals: single quotes, double quotes, and template literals (backticks). All three produce valid strings, but each has its own advantages depending on the context.

Example: String Creation with Single and Double Quotes

// Single quotes
let firstName = 'Alice';

// Double quotes
let lastName = "Johnson";

// You can nest one type inside the other
let sentence = "She said, 'Hello!'";
let another = 'He replied, "Hi there!"';

// Escape characters when using the same quote type
let escaped = 'It\'s a beautiful day';
let escapedDouble = "She said, \"Goodbye!\"";

console.log(firstName);   // Alice
console.log(sentence);    // She said, 'Hello!'
console.log(escaped);     // It's a beautiful day

Single quotes and double quotes behave identically in JavaScript. The choice between them is a matter of personal or team preference. Many style guides recommend picking one and sticking with it consistently throughout your codebase.

Template Literals

Template literals, introduced in ES6 (ES2015), use backtick characters and provide powerful features that regular quotes do not offer. They support string interpolation, multi-line strings, and embedded expressions.

Example: Template Literals and String Interpolation

let name = 'Alice';
let age = 28;

// String interpolation with ${}
let greeting = `Hello, my name is ${name} and I am ${age} years old.`;
console.log(greeting);
// Hello, my name is Alice and I am 28 years old.

// Expressions inside template literals
let price = 19.99;
let quantity = 3;
let total = `Total: $${(price * quantity).toFixed(2)}`;
console.log(total);  // Total: $59.97

// Multi-line strings
let poem = `Roses are red,
Violets are blue,
JavaScript is awesome,
And so are you.`;
console.log(poem);

// Without template literals, you would need concatenation or escape characters
let oldWay = 'Roses are red,\n' +
             'Violets are blue,\n' +
             'JavaScript is awesome,\n' +
             'And so are you.';

// Tagged template literals (advanced)
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => {
        return result + str + (values[i] ? `<strong>${values[i]}</strong>` : '');
    }, '');
}
let highlighted = highlight`Welcome ${name}, you are ${age}!`;
console.log(highlighted);
// Welcome <strong>Alice</strong>, you are <strong>28</strong>!
Pro Tip: Template literals are the preferred way to build strings that include variables or expressions. They make your code more readable than string concatenation with the + operator. Use them whenever you need to embed values inside a string.

String Immutability

An important concept to understand is that strings in JavaScript are immutable. This means that once a string is created, you cannot change individual characters within it. Any operation that appears to modify a string actually creates a brand new string.

Example: Strings Are Immutable

let word = 'Hello';

// Trying to change a character does NOT work
word[0] = 'J';
console.log(word);  // Hello (unchanged!)

// You must create a new string instead
word = 'J' + word.slice(1);
console.log(word);  // Jello

// Methods always return NEW strings
let original = 'JavaScript';
let upper = original.toUpperCase();
console.log(original);  // JavaScript (unchanged)
console.log(upper);     // JAVASCRIPT (new string)
Common Mistake: Beginners often try to modify a string by assigning to an index like str[0] = 'X'. This fails silently in normal mode and throws an error in strict mode. Always remember that string methods return new strings -- they never modify the original.

String Length

The length property returns the number of UTF-16 code units in a string. For most common characters, this equals the number of characters. Be aware that certain characters like emojis may count as two or more code units.

Example: String Length Property

let text = 'JavaScript';
console.log(text.length);  // 10

let empty = '';
console.log(empty.length);  // 0

let spaces = '  Hello  ';
console.log(spaces.length);  // 9 (spaces count!)

let emoji = 'Hello 😀';
console.log(emoji.length);  // 8 (emoji uses 2 code units!)

Accessing Characters: charAt and Bracket Notation

You can access individual characters in a string using the charAt() method or bracket notation. Both use zero-based indexing, meaning the first character is at position 0.

Example: Accessing Characters

let str = 'JavaScript';

// Using charAt()
console.log(str.charAt(0));   // J
console.log(str.charAt(4));   // S
console.log(str.charAt(20));  // '' (empty string for out-of-range)

// Using bracket notation
console.log(str[0]);   // J
console.log(str[4]);   // S
console.log(str[20]);  // undefined (not empty string!)

// Last character
console.log(str.charAt(str.length - 1));  // t
console.log(str[str.length - 1]);         // t

// Using at() method (ES2022) - supports negative indexing
console.log(str.at(0));    // J
console.log(str.at(-1));   // t (last character)
console.log(str.at(-2));   // p (second to last)
Note: The key difference between charAt() and bracket notation is what they return for out-of-range indices. charAt() returns an empty string '', while bracket notation returns undefined. The newer at() method supports negative indices, making it very convenient for accessing characters from the end of a string.

Searching Within Strings: indexOf, lastIndexOf, and includes

JavaScript provides several methods for finding text within a string. These are essential for tasks like validation, parsing, and conditional logic based on string content.

Example: indexOf and lastIndexOf

let sentence = 'The quick brown fox jumps over the lazy dog';

// indexOf - finds first occurrence (returns index or -1)
console.log(sentence.indexOf('fox'));      // 16
console.log(sentence.indexOf('the'));      // 31 (case-sensitive!)
console.log(sentence.indexOf('The'));      // 0
console.log(sentence.indexOf('cat'));      // -1 (not found)

// indexOf with start position (search from index)
console.log(sentence.indexOf('o', 15));    // 17 (finds 'o' in 'fox')
console.log(sentence.indexOf('o', 18));    // 26 (finds 'o' in 'over')

// lastIndexOf - finds last occurrence (searches backwards)
console.log(sentence.lastIndexOf('o'));    // 41 (the 'o' in 'dog')
console.log(sentence.lastIndexOf('the'));  // 31

// Common pattern: check if substring exists
if (sentence.indexOf('fox') !== -1) {
    console.log('Found the fox!');
}

Example: includes, startsWith, and endsWith

let url = 'https://www.example.com/products?page=1';

// includes - returns boolean
console.log(url.includes('https'));    // true
console.log(url.includes('http://')); // false
console.log(url.includes('example')); // true

// startsWith - checks beginning of string
console.log(url.startsWith('https://'));  // true
console.log(url.startsWith('http://'));   // false
console.log(url.startsWith('www', 8));   // true (from position 8)

// endsWith - checks end of string
let file = 'document.pdf';
console.log(file.endsWith('.pdf'));    // true
console.log(file.endsWith('.doc'));    // false
console.log(file.endsWith('ment', 8)); // true (checks first 8 chars)

// Practical example: validating file types
function isImageFile(filename) {
    let lower = filename.toLowerCase();
    return lower.endsWith('.jpg') ||
           lower.endsWith('.jpeg') ||
           lower.endsWith('.png') ||
           lower.endsWith('.gif') ||
           lower.endsWith('.webp');
}

console.log(isImageFile('photo.JPG'));   // true
console.log(isImageFile('data.csv'));    // false
Pro Tip: Prefer includes() over indexOf() !== -1 for readability when you only need to check whether a substring exists. Use indexOf() when you need the actual position of the match.

Extracting Substrings: slice and substring

Two commonly used methods for extracting parts of a string are slice() and substring(). Both accept start and end parameters and return a new string, but they handle edge cases differently.

Example: slice and substring

let text = 'JavaScript is awesome';

// slice(start, end) - end is exclusive
console.log(text.slice(0, 10));    // JavaScript
console.log(text.slice(11));       // is awesome (to end)
console.log(text.slice(0, 4));     // Java

// slice with negative indices (counts from end)
console.log(text.slice(-7));       // awesome
console.log(text.slice(-7, -1));   // awesom
console.log(text.slice(-11, -8));  // is

// substring(start, end) - end is exclusive
console.log(text.substring(0, 10));  // JavaScript
console.log(text.substring(11));     // is awesome

// Key difference: substring swaps arguments if start > end
console.log(text.substring(10, 0));  // JavaScript (swapped to 0, 10)
console.log(text.slice(10, 0));      // '' (empty string, no swap)

// substring treats negatives as 0
console.log(text.substring(-5));     // JavaScript is awesome (treated as 0)
console.log(text.slice(-5));         // esome (5 chars from end)

// Practical example: extract file extension
function getExtension(filename) {
    let dotIndex = filename.lastIndexOf('.');
    if (dotIndex === -1) return '';
    return filename.slice(dotIndex + 1).toLowerCase();
}

console.log(getExtension('photo.JPG'));      // jpg
console.log(getExtension('archive.tar.gz')); // gz
console.log(getExtension('README'));          // ''
Note: In modern JavaScript, slice() is generally preferred over substring() because its behavior with negative indices is more intuitive and useful. The older substr() method is deprecated and should be avoided in new code.

Replacing Content: replace and replaceAll

The replace() method allows you to substitute parts of a string with new content. By default, it replaces only the first occurrence. The replaceAll() method, introduced in ES2021, replaces every occurrence.

Example: replace and replaceAll

let message = 'I love cats. Cats are great. I have two cats.';

// replace - only first occurrence
console.log(message.replace('cats', 'dogs'));
// I love dogs. Cats are great. I have two cats.

// replace is case-sensitive
console.log(message.replace('Cats', 'Dogs'));
// I love cats. Dogs are great. I have two cats.

// replaceAll - all occurrences (case-sensitive)
console.log(message.replaceAll('cats', 'dogs'));
// I love dogs. Cats are great. I have two dogs.

// Using regex with global flag for replace all (before replaceAll existed)
console.log(message.replace(/cats/gi, 'dogs'));
// I love dogs. dogs are great. I have two dogs.

// Replace with a function
let prices = 'Item A: $10, Item B: $25, Item C: $5';
let doubled = prices.replace(/\$(\d+)/g, function(match, amount) {
    return '$' + (parseInt(amount) * 2);
});
console.log(doubled);
// Item A: $20, Item B: $50, Item C: $10

// Practical example: sanitize user input
function sanitizeHTML(input) {
    return input
        .replaceAll('&', '&amp;')
        .replaceAll('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;')
        .replaceAll("'", '&#039;');
}

let userInput = '<script>alert("XSS")</script>';
console.log(sanitizeHTML(userInput));
// &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

Splitting and Joining Strings

The split() method divides a string into an array of substrings based on a delimiter. The join() method on arrays does the reverse -- it combines array elements into a single string. Together, they form a powerful duo for string manipulation.

Example: split and join

// Basic split
let csv = 'apple,banana,cherry,date';
let fruits = csv.split(',');
console.log(fruits);  // ['apple', 'banana', 'cherry', 'date']

// Split with limit
let limited = csv.split(',', 2);
console.log(limited);  // ['apple', 'banana']

// Split into individual characters
let chars = 'Hello'.split('');
console.log(chars);  // ['H', 'e', 'l', 'l', 'o']

// Split by whitespace
let words = '  Hello   World  '.trim().split(/\s+/);
console.log(words);  // ['Hello', 'World']

// join - combine array into string
let joined = fruits.join(' - ');
console.log(joined);  // apple - banana - cherry - date

let noSeparator = ['J', 'S'].join('');
console.log(noSeparator);  // JS

// Practical example: convert to title case
function toTitleCase(str) {
    return str
        .toLowerCase()
        .split(' ')
        .map(word => word.charAt(0).toUpperCase() + word.slice(1))
        .join(' ');
}

console.log(toTitleCase('hello world from javascript'));
// Hello World From Javascript

// Practical example: convert slug to readable title
function slugToTitle(slug) {
    return slug.split('-').map(word =>
        word.charAt(0).toUpperCase() + word.slice(1)
    ).join(' ');
}

console.log(slugToTitle('my-awesome-blog-post'));
// My Awesome Blog Post

Trimming Whitespace

Whitespace management is critical when handling user input, parsing data, or comparing strings. JavaScript provides three trimming methods to remove unwanted spaces from strings.

Example: trim, trimStart, and trimEnd

let padded = '   Hello, World!   ';

// trim - removes whitespace from both ends
console.log(padded.trim());       // 'Hello, World!'
console.log(padded.trim().length); // 13

// trimStart (also known as trimLeft) - removes from beginning
console.log(padded.trimStart());  // 'Hello, World!   '

// trimEnd (also known as trimRight) - removes from end
console.log(padded.trimEnd());    // '   Hello, World!'

// Practical example: clean form input
function cleanInput(value) {
    return value.trim().replace(/\s+/g, ' ');
}

console.log(cleanInput('   John    Doe   '));  // 'John Doe'

Padding Strings: padStart and padEnd

The padStart() and padEnd() methods pad a string with characters until it reaches a specified length. These are incredibly useful for formatting output like numbers, dates, and aligned text.

Example: padStart and padEnd

// padStart - adds padding at the beginning
let num = '5';
console.log(num.padStart(3, '0'));   // 005
console.log(num.padStart(5, '0'));   // 00005
console.log(num.padStart(1, '0'));   // 5 (already long enough)

// padEnd - adds padding at the end
let item = 'Apple';
console.log(item.padEnd(10, '.'));   // Apple.....
console.log(item.padEnd(10));        // 'Apple     ' (default pad is space)

// Practical example: format a price list
let items = [
    { name: 'Coffee', price: 4.50 },
    { name: 'Sandwich', price: 12.00 },
    { name: 'Cake', price: 8.75 }
];

items.forEach(item => {
    let name = item.name.padEnd(15, '.');
    let price = item.price.toFixed(2).padStart(8);
    console.log(`${name}$${price}`);
});
// Coffee.........$    4.50
// Sandwich.......$   12.00
// Cake...........$    8.75

// Practical example: mask a credit card number
function maskCard(cardNumber) {
    let last4 = cardNumber.slice(-4);
    return last4.padStart(cardNumber.length, '*');
}

console.log(maskCard('4532015112830366'));
// ************0366

Repeating Strings

The repeat() method creates a new string by repeating the original string a specified number of times. This is handy for generating patterns, creating separators, or building visual elements.

Example: repeat Method

console.log('Ha'.repeat(3));     // HaHaHa
console.log('-'.repeat(30));     // ------------------------------
console.log('abc'.repeat(0));    // '' (empty string)

// Practical example: create a simple progress bar
function progressBar(percent, width = 20) {
    let filled = Math.round(width * percent / 100);
    let empty = width - filled;
    return '[' + '#'.repeat(filled) + '-'.repeat(empty) + ']' + ` ${percent}%`;
}

console.log(progressBar(75));   // [###############-----] 75%
console.log(progressBar(30));   // [######--------------] 30%
console.log(progressBar(100));  // [####################] 100%

Case Conversion

JavaScript provides methods to convert strings to uppercase or lowercase. These are essential for case-insensitive comparisons and formatting.

Example: toUpperCase and toLowerCase

let mixed = 'Hello World';
console.log(mixed.toUpperCase());  // HELLO WORLD
console.log(mixed.toLowerCase());  // hello world

// Case-insensitive comparison
let input = 'JavaScript';
let expected = 'javascript';
console.log(input === expected);                        // false
console.log(input.toLowerCase() === expected.toLowerCase()); // true

// Practical example: case-insensitive search
function searchArray(arr, query) {
    let lowerQuery = query.toLowerCase();
    return arr.filter(item => item.toLowerCase().includes(lowerQuery));
}

let languages = ['JavaScript', 'Python', 'Java', 'TypeScript'];
console.log(searchArray(languages, 'java'));
// ['JavaScript', 'Java']

// Locale-aware case conversion
let turkish = 'Istanbul';
console.log(turkish.toLocaleLowerCase('tr')); // istanbul (with dotless i)
console.log(turkish.toLocaleLowerCase('en')); // istanbul

String Comparison

Strings in JavaScript are compared character by character using their Unicode code point values. Understanding this is vital for sorting and ordering operations. The localeCompare() method provides locale-aware comparison that respects language-specific rules.

Example: String Comparison

// Basic comparison (uses Unicode code points)
console.log('a' < 'b');     // true
console.log('B' < 'a');     // true (uppercase letters come first in Unicode)
console.log('10' < '9');    // true (compares character by character: '1' < '9')

// localeCompare for proper sorting
let words = ['banana', 'Apple', 'cherry'];

// Default sort (case-sensitive, Unicode order)
console.log(words.sort());
// ['Apple', 'banana', 'cherry'] (uppercase A comes before lowercase b)

// Locale-aware sort
words.sort((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }));
console.log(words);
// ['Apple', 'banana', 'cherry'] (proper alphabetical order)

// localeCompare returns -1, 0, or 1
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)

// Numeric sorting with localeCompare
let files = ['file10', 'file2', 'file1', 'file20'];
files.sort((a, b) => a.localeCompare(b, 'en', { numeric: true }));
console.log(files);
// ['file1', 'file2', 'file10', 'file20']

Unicode and Emoji Handling

JavaScript strings are internally encoded as UTF-16. Most common characters use a single 16-bit code unit, but characters outside the Basic Multilingual Plane (BMP), including most emojis, use two code units called a surrogate pair. This has important implications for string operations.

Example: Unicode and Emoji Handling

// Regular characters: 1 code unit each
let hello = 'Hello';
console.log(hello.length);  // 5

// Emojis: 2 code units (surrogate pair)
let smile = '😀';
console.log(smile.length);      // 2 (not 1!)
console.log(smile.charAt(0));   // shows half of surrogate pair
console.log(smile.codePointAt(0)); // 128512 (correct code point)

// Iterating with for...of handles surrogates correctly
let text = 'Hi 😀!';
for (let char of text) {
    console.log(char);
}
// H, i, (space), 😀, !

// Spread operator also handles surrogates
let characters = [...'Hi 😀!'];
console.log(characters);        // ['H', 'i', ' ', '😀', '!']
console.log(characters.length); // 5 (correct count!)

// Getting true character count
function trueLength(str) {
    return [...str].length;
}

console.log(trueLength('Hello 😀 World 🌍'));  // 15

// Unicode escape sequences
let omega = '\u03A9';          // Greek capital omega
console.log(omega);             // Ω

let rocket = '\u{1F680}';      // Unicode code point escape (ES6)
console.log(rocket);            // (rocket emoji)

// Normalizing Unicode (important for comparison)
let cafe1 = 'caf\u00e9';      // e with accent (single code point)
let cafe2 = 'cafe\u0301';     // e + combining accent (two code points)
console.log(cafe1 === cafe2);           // false (different representations!)
console.log(cafe1.normalize() === cafe2.normalize()); // true
Common Mistake: Using .length or charAt() on strings containing emojis or special Unicode characters often gives unexpected results. Always use [...str].length for true character count and for...of loops for proper iteration over strings with emojis.

String Concatenation Methods

There are several ways to combine strings in JavaScript. Understanding the options helps you pick the most readable and efficient approach for each situation.

Example: String Concatenation Approaches

let first = 'Hello';
let second = 'World';

// Plus operator
let result1 = first + ' ' + second;
console.log(result1);  // Hello World

// Template literal (recommended for readability)
let result2 = `${first} ${second}`;
console.log(result2);  // Hello World

// concat method (less common)
let result3 = first.concat(' ', second);
console.log(result3);  // Hello World

// Building strings in a loop -- use array + join for efficiency
let parts = [];
for (let i = 1; i <= 5; i++) {
    parts.push(`Item ${i}`);
}
let list = parts.join(', ');
console.log(list);  // Item 1, Item 2, Item 3, Item 4, Item 5
Pro Tip: When building strings in a loop, avoid repeated concatenation with +=. Instead, push parts into an array and use join() at the end. This pattern is cleaner and performs better for large numbers of iterations.

Practical String Patterns

Let us look at some real-world patterns that combine multiple string methods. These demonstrate how the methods work together to solve common programming tasks.

Example: Common Real-World String Operations

// Convert camelCase to kebab-case
function camelToKebab(str) {
    return str.replace(/([A-Z])/g, '-$1').toLowerCase();
}
console.log(camelToKebab('backgroundColor'));  // background-color
console.log(camelToKebab('fontSize'));          // font-size

// Truncate text with ellipsis
function truncate(str, maxLength) {
    if (str.length <= maxLength) return str;
    return str.slice(0, maxLength - 3) + '...';
}
console.log(truncate('This is a very long sentence', 15));
// This is a ve...

// Count occurrences of a substring
function countOccurrences(str, sub) {
    return str.split(sub).length - 1;
}
console.log(countOccurrences('banana', 'an'));  // 2

// Reverse a string
function reverseString(str) {
    return [...str].reverse().join('');
}
console.log(reverseString('Hello'));     // olleH
console.log(reverseString('Hello 😀')); // 😀 olleH (emoji safe!)

// Check if string is a palindrome
function isPalindrome(str) {
    let cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
    return cleaned === reverseString(cleaned);
}
console.log(isPalindrome('racecar'));                // true
console.log(isPalindrome('A man a plan a canal Panama')); // true

Practice Exercise

Build a string utility library by creating the following functions. Test each one with at least three different inputs:

  1. capitalize(str) -- Takes a sentence and capitalizes the first letter of every word (e.g., "hello world" becomes "Hello World").
  2. countWords(str) -- Counts the number of words in a string, handling multiple spaces and leading/trailing whitespace correctly.
  3. slugify(str) -- Converts a string to a URL-friendly slug by converting to lowercase, replacing spaces with hyphens, and removing special characters (e.g., "Hello World! 2024" becomes "hello-world-2024").
  4. extractEmails(str) -- Finds and returns all email addresses from a block of text as an array (hint: use split and filter with includes).
  5. maskEmail(email) -- Masks an email address by showing only the first two characters and the domain (e.g., "alice@example.com" becomes "al***@example.com").

For each function, think about edge cases: empty strings, strings with only spaces, strings with special characters, and strings with unusual formatting. Use the methods you learned in this lesson -- split, join, trim, slice, replace, toLowerCase, includes, padEnd, and others.

ES
Edrees Salih
11 hours ago

We are still cooking the magic in the way!