🔤 Lesson 12: Strings & Template Literals
Text is everywhere in web development — user input, API data, HTML content. Master JavaScript's string tools and you'll handle text like a pro.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Use template literals for interpolation and multiline strings
- Search strings with
includes,startsWith,endsWith, andindexOf - Extract parts of strings with
slice,substring, andat - Transform strings with
toUpperCase,toLowerCase,trim, andreplace - Split strings into arrays and join arrays into strings
- Pad, repeat, and format strings for display
- Understand that strings are immutable
Estimated Time: 40 minutes
Project: Build a text formatting utility
📑 In This Lesson
Introduction
You've been using strings since Lesson 3, but we've only scratched the surface. Strings have dozens of built-in methods for searching, extracting, and transforming text. In this lesson, you'll learn the ones you'll use most often.
One critical concept first: strings are immutable. Every string method returns a new string — the original is never changed. This is different from arrays, where methods like push and splice modify the original.
const greeting = "Hello, World!";
// String methods return NEW strings
const upper = greeting.toUpperCase();
console.log(upper); // "HELLO, WORLD!"
console.log(greeting); // "Hello, World!" — unchanged!
💡 Strings Are Like Read-Only Arrays
You can access individual characters by index (str[0]) and check length, just like arrays. But you can't change individual characters — you have to create a new string.
const word = "hello";
console.log(word[0]); // "h"
console.log(word.length); // 5
// word[0] = "H"; // ❌ Silently fails — strings are immutable
Template Literals
Template literals use backticks (`) instead of quotes. You've used them for basic interpolation, but they have more features worth knowing.
String Interpolation (Review)
const name = "Ray";
const age = 30;
// Old way — concatenation
const msg1 = "Hello, " + name + "! You are " + age + " years old.";
// Template literal — much cleaner
const msg2 = `Hello, ${name}! You are ${age} years old.`;
// Expressions work inside ${}
const msg3 = `In 10 years, you'll be ${age + 10}.`;
const msg4 = `Status: ${age >= 18 ? "adult" : "minor"}`;
Multiline Strings
// Old way — escaped newlines
const old = "Line 1\n" +
"Line 2\n" +
"Line 3";
// Template literal — just press Enter
const modern = `Line 1
Line 2
Line 3`;
// Great for HTML snippets
const card = `
<div class="card">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>
`;
Tagged Templates (Preview)
You can "tag" a template literal with a function. This is an advanced feature used in libraries — you'll recognize it when you see it.
// You'll encounter these in frameworks and libraries
// css`color: red;` — styled-components
// html`<div>${content}</div>` — lit-html
// gql`query { user { name } }` — GraphQL
// The function receives the string parts and values separately
// For now, just know they exist — you don't need to write them yet
Searching Strings
includes() — Does It Contain?
const sentence = "The quick brown fox jumps over the lazy dog";
console.log(sentence.includes("fox")); // true
console.log(sentence.includes("cat")); // false
console.log(sentence.includes("Fox")); // false — case-sensitive!
// Case-insensitive check
console.log(sentence.toLowerCase().includes("fox")); // true
startsWith() and endsWith()
const filename = "report-2026.pdf";
console.log(filename.startsWith("report")); // true
console.log(filename.endsWith(".pdf")); // true
console.log(filename.endsWith(".docx")); // false
// Practical: validate file types
function isImage(name) {
const lower = name.toLowerCase();
return lower.endsWith(".jpg") || lower.endsWith(".png") || lower.endsWith(".gif");
}
console.log(isImage("photo.JPG")); // true
console.log(isImage("doc.pdf")); // false
indexOf() and lastIndexOf()
const text = "Hello World, Hello JavaScript";
console.log(text.indexOf("Hello")); // 0 (first occurrence)
console.log(text.lastIndexOf("Hello")); // 13 (last occurrence)
console.log(text.indexOf("Python")); // -1 (not found)
// Search starting from a specific position
console.log(text.indexOf("Hello", 1)); // 13 (skips the first one)
| Method | Returns | Use Case |
|---|---|---|
includes(str) | true / false | Does it contain this text? |
startsWith(str) | true / false | Does it begin with this? |
endsWith(str) | true / false | Does it end with this? |
indexOf(str) | Index or -1 | Where is the first occurrence? |
lastIndexOf(str) | Index or -1 | Where is the last occurrence? |
Extracting Parts
slice() — The Go-To Extractor
Just like array slice, string slice extracts a portion without changing the original. It supports negative indexes.
const str = "Hello, World!";
console.log(str.slice(0, 5)); // "Hello"
console.log(str.slice(7)); // "World!"
console.log(str.slice(-6)); // "orld!"
console.log(str.slice(7, 12)); // "World"
console.log(str.slice(-6, -1)); // "orld"
substring()
Similar to slice but doesn't support negative indexes. Use slice instead — it's more versatile.
const str = "Hello, World!";
console.log(str.substring(0, 5)); // "Hello"
console.log(str.substring(7)); // "World!"
// substring swaps arguments if start > end
console.log(str.substring(5, 0)); // "Hello" (same as (0, 5))
// slice doesn't — it returns empty string
console.log(str.slice(5, 0)); // ""
at() — Modern Character Access
const word = "JavaScript";
console.log(word.at(0)); // "J"
console.log(word.at(-1)); // "t" (last character)
console.log(word.at(-2)); // "p" (second to last)
Transforming Strings
Case Conversion
const mixed = "Hello World";
console.log(mixed.toUpperCase()); // "HELLO WORLD"
console.log(mixed.toLowerCase()); // "hello world"
// Common: case-insensitive comparison
const input = "Yes";
if (input.toLowerCase() === "yes") {
console.log("User said yes!");
}
trim() — Remove Whitespace
const messy = " Hello World ";
console.log(messy.trim()); // "Hello World" (both sides)
console.log(messy.trimStart()); // "Hello World " (left only)
console.log(messy.trimEnd()); // " Hello World" (right only)
// Essential for user input
const email = " ray@example.com ";
const clean = email.trim();
console.log(clean); // "ray@example.com"
replace() and replaceAll()
const text = "I love cats. cats are great. I have two cats.";
// replace — first occurrence only
console.log(text.replace("cats", "dogs"));
// "I love dogs. cats are great. I have two cats."
// replaceAll — every occurrence
console.log(text.replaceAll("cats", "dogs"));
// "I love dogs. dogs are great. I have two dogs."
// Replace with regex (for pattern matching)
const phone = "(555) 123-4567";
const digits = phone.replace(/\D/g, ""); // Remove non-digits
console.log(digits); // "5551234567"
💡 Regular Expressions — A Preview
The /\D/g syntax is a regular expression (regex). Regex is a powerful pattern-matching language. You don't need to learn it now — just know that replace supports it for advanced text transformations. The g flag means "global" (replace all matches).
Split & Join
These two methods are perfect partners: split turns a string into an array, and join turns an array back into a string.
split() — String to Array
const csv = "apple,banana,cherry,date";
const fruits = csv.split(",");
console.log(fruits); // ["apple", "banana", "cherry", "date"]
const sentence = "The quick brown fox";
const words = sentence.split(" ");
console.log(words); // ["The", "quick", "brown", "fox"]
// Split into characters
const letters = "hello".split("");
console.log(letters); // ["h", "e", "l", "l", "o"]
// Limit the number of splits
const parts = "a-b-c-d-e".split("-", 3);
console.log(parts); // ["a", "b", "c"]
join() — Array to String
const words = ["Hello", "World"];
console.log(words.join(" ")); // "Hello World"
console.log(words.join(", ")); // "Hello, World"
console.log(words.join("-")); // "Hello-World"
console.log(words.join("")); // "HelloWorld"
split + transform + join Pattern
One of the most useful string manipulation patterns — split a string apart, transform the pieces, and join them back.
// Capitalize each word
function titleCase(str) {
return str
.split(" ")
.map(word => word[0].toUpperCase() + word.slice(1).toLowerCase())
.join(" ");
}
console.log(titleCase("hello world")); // "Hello World"
console.log(titleCase("the QUICK brown FOX")); // "The Quick Brown Fox"
// Convert between formats
function camelToKebab(str) {
return str
.replace(/([A-Z])/g, "-$1")
.toLowerCase();
}
console.log(camelToKebab("backgroundColor")); // "background-color"
console.log(camelToKebab("fontSize")); // "font-size"
Padding & Repeating
padStart() and padEnd()
Pad a string to a target length by adding characters at the start or end. Great for formatting numbers, IDs, and tabular output.
// Pad numbers with leading zeros
const orderNum = "42";
console.log(orderNum.padStart(6, "0")); // "000042"
// Align text
const items = [
{ name: "Apple", price: "1.50" },
{ name: "Banana", price: "0.75" },
{ name: "Cherry", price: "3.00" }
];
items.forEach(item => {
console.log(`${item.name.padEnd(10)} $${item.price.padStart(5)}`);
});
// Apple $ 1.50
// Banana $ 0.75
// Cherry $ 3.00
// Mask credit card numbers
const card = "4532015112830366";
const masked = card.slice(-4).padStart(card.length, "*");
console.log(masked); // "************0366"
repeat()
console.log("ha".repeat(3)); // "hahaha"
console.log("-".repeat(20)); // "--------------------"
console.log("abc".repeat(0)); // ""
// Create a simple progress bar
function progressBar(percent, width = 20) {
const filled = Math.round(width * percent / 100);
const empty = width - filled;
return `[${"█".repeat(filled)}${"░".repeat(empty)}] ${percent}%`;
}
console.log(progressBar(75)); // [███████████████░░░░░] 75%
console.log(progressBar(30)); // [██████░░░░░░░░░░░░░░] 30%
Hands-on Exercise
🏋️ Exercise: Text Formatting Utility
Objective: Build a collection of string utility functions.
// TODO: Write each function using string methods (no manual character loops!)
// 1. truncate(str, maxLength)
// Shorten a string and add "..." if it exceeds maxLength
// truncate("Hello World", 8) → "Hello..."
// truncate("Hi", 10) → "Hi"
// 2. slugify(str)
// Convert a title to a URL-friendly slug
// slugify("Hello World!") → "hello-world"
// slugify(" My Blog Post #1 ") → "my-blog-post-1"
// 3. countWords(str)
// Count the number of words in a string
// countWords("Hello World") → 2
// countWords(" lots of spaces ") → 3
// 4. initials(fullName)
// Get initials from a full name
// initials("Ray De La Paz") → "RDLP"
// initials("Alice") → "A"
// 5. maskEmail(email)
// Mask an email address for privacy
// maskEmail("ray@example.com") → "r***@example.com"
// maskEmail("alice@gmail.com") → "a****@gmail.com"
💡 Hint
truncate: Check length > maxLength, then use slice(0, maxLength - 3) + "...".
slugify: Chain: trim() → toLowerCase() → replace(/[^a-z0-9\s-]/g, "") → replace(/\s+/g, "-").
countWords: trim() → split(/\s+/) → length. Handle empty string edge case.
initials: split(" ") → map(w => w[0]) → join("") → toUpperCase().
maskEmail: split("@") to get local and domain parts, then mask the local part.
✅ Solution
// 1. Truncate
function truncate(str, maxLength) {
if (str.length <= maxLength) return str;
return str.slice(0, maxLength - 3) + "...";
}
console.log(truncate("Hello World", 8)); // "Hello..."
console.log(truncate("Hi", 10)); // "Hi"
// 2. Slugify
function slugify(str) {
return str
.trim()
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, "") // Remove special chars
.replace(/\s+/g, "-") // Spaces to hyphens
.replace(/-+/g, "-"); // Collapse multiple hyphens
}
console.log(slugify("Hello World!")); // "hello-world"
console.log(slugify(" My Blog Post #1 ")); // "my-blog-post-1"
// 3. Count Words
function countWords(str) {
const trimmed = str.trim();
if (trimmed === "") return 0;
return trimmed.split(/\s+/).length;
}
console.log(countWords("Hello World")); // 2
console.log(countWords(" lots of spaces ")); // 3
console.log(countWords("")); // 0
// 4. Initials
function initials(fullName) {
return fullName
.trim()
.split(/\s+/)
.map(word => word[0])
.join("")
.toUpperCase();
}
console.log(initials("Ray De La Paz")); // "RDLP"
console.log(initials("Alice")); // "A"
// 5. Mask Email
function maskEmail(email) {
const [local, domain] = email.split("@");
const masked = local[0] + "*".repeat(local.length - 1);
return `${masked}@${domain}`;
}
console.log(maskEmail("ray@example.com")); // "r**@example.com"
console.log(maskEmail("alice@gmail.com")); // "a****@gmail.com"
🎯 Quick Quiz
Question 1: Are JavaScript strings mutable or immutable?
Question 2: What does "hello world".split(" ") return?
Question 3: How do you check if a string ends with ".pdf"?
Summary
🎉 Key Takeaways
- Strings are immutable — every method returns a new string
- Template literals (
`backticks`) support interpolation and multiline strings - Searching:
includes,startsWith,endsWith,indexOf - Extracting:
sliceis your go-to (supports negative indexes) - Transforming:
toUpperCase,toLowerCase,trim,replace,replaceAll - split + map + join is a powerful pattern for word-level transformations
padStart/padEndformat strings to a fixed width- Always
trim()user input before processing it
📚 Additional Resources
🚀 What's Next?
You've completed Module 3! You now know how to work with all of JavaScript's core data structures — arrays, objects, and strings. In Module 4, you'll connect JavaScript to the web page itself using the DOM (Document Object Model). This is where JavaScript goes from a scripting exercise to building real, interactive web pages.
🎉 Module 3 Complete!
You've mastered working with data — arrays, objects, array methods, and strings. Your JavaScript toolkit is now ready for the DOM. Time to make web pages come alive!