Guide to JavaScript Template Literals & Strings
Master JavaScript template literals and string methods with practical examples. Learn string interpolation, tagged templates, common string operations, and modern patterns for building dynamic text.
Strings are one of the most frequently used data types in JavaScript. From displaying user names to building API URLs to formatting error messages, string operations are everywhere. Template literals, introduced in ES2015, transformed how developers work with strings by adding interpolation, multi-line support, and tagged template functionality. This guide covers everything from basic string creation to advanced template patterns you will use in production code.
Creating Strings: Three Syntaxes
JavaScript offers three ways to create strings. Each uses a different delimiter character.
const single = 'Hello, World!'; // Single quotes
const double = "Hello, World!"; // Double quotes
const template = `Hello, World!`; // Backticks (template literal)Single and double quotes are functionally identical. They create the same string with the same methods and properties. The choice between them is purely a style convention (most projects pick one and stick with it).
Template literal (backticks) offer additional features that single and double quotes cannot provide.
| Feature | Single/Double Quotes | Template Literals |
|---|---|---|
| Basic strings | Yes | Yes |
| String interpolation | No | Yes (${expression}) |
| Multi-line strings | No (requires \n) | Yes (literal newlines) |
| Embedded expressions | No | Yes |
| Tagged templates | No | Yes |
| Escape sequences | Yes | Yes |
String Interpolation with Template Literals
String interpolation lets you embed expressions directly inside a string using ${expression} syntax. This replaces the clunky string concatenation approach.
const userName = "Ada";
const itemCount = 3;
const unitPrice = 29.99;
// Old approach: concatenation with +
const message1 = "Hello, " + userName + "! You have " + itemCount + " items ($" + (itemCount * unitPrice).toFixed(2) + ").";
// Modern approach: template literal interpolation
const message2 = `Hello, ${userName}! You have ${itemCount} items ($${(itemCount * unitPrice).toFixed(2)}).`;
console.log(message2);
// "Hello, Ada! You have 3 items ($89.97)."The ${} syntax accepts any JavaScript expression, not just variables:
const a = 10;
const b = 20;
console.log(`Sum: ${a + b}`); // "Sum: 30"
console.log(`Max: ${Math.max(a, b)}`); // "Max: 20"
console.log(`Even: ${a % 2 === 0}`); // "Even: true"
console.log(`Type: ${typeof a}`); // "Type: number"
// Function calls inside interpolation
const greet = (name) => `Hello, ${name}!`;
console.log(`${greet("Ada")}`); // "Hello, Ada!"
// Ternary expressions
const status = true;
console.log(`User is ${status ? "active" : "inactive"}`);
// "User is active"Nesting Template Literals
You can nest template literals inside interpolation expressions for complex string building:
const users = [
{ name: "Ada", role: "admin" },
{ name: "Grace", role: "editor" },
{ name: "Alan", role: "viewer" },
];
const userList = `
Users:
${users.map((u) => ` - ${u.name} (${u.role})`).join("\n")}
`;
console.log(userList);
// Users:
// - Ada (admin)
// - Grace (editor)
// - Alan (viewer)Multi-Line Strings
Template literals preserve line breaks exactly as written. With single or double quotes, you need escape sequences.
// With single/double quotes (escape sequences required)
const html1 = "<div>\n <h1>Title</h1>\n <p>Content</p>\n</div>";
// With template literals (literal newlines preserved)
const html2 = `<div>
<h1>Title</h1>
<p>Content</p>
</div>`;
console.log(html1 === html2); // trueFor a deeper exploration of multi-line string techniques, including how to handle indentation and whitespace, see the multi-line strings guide.
Whitespace Matters
Template literals preserve ALL whitespace, including indentation. If your template is indented inside a function, that indentation becomes part of the string. Use .trim() or a dedent utility to clean up unwanted leading whitespace.
Essential String Methods
JavaScript provides a rich set of methods on the String prototype. Here are the ones you will use most frequently:
Searching and Testing
const email = "ada.lovelace@runehub.dev";
// includes() - check if substring exists
console.log(email.includes("@")); // true
console.log(email.includes("yahoo")); // false
// startsWith() / endsWith()
console.log(email.startsWith("ada")); // true
console.log(email.endsWith(".dev")); // true
// indexOf() - find position (returns -1 if not found)
console.log(email.indexOf("@")); // 13
console.log(email.indexOf("xyz")); // -1
// search() - find with regex
console.log(email.search(/\d/)); // -1 (no digits)
console.log(email.search(/@/)); // 13Extracting and Slicing
const filePath = "/users/ada/documents/report.pdf";
// slice(start, end) - extract substring (negative indices count from end)
console.log(filePath.slice(0, 6)); // "/users"
console.log(filePath.slice(-10)); // "report.pdf"
console.log(filePath.slice(1, -1)); // "users/ada/documents/report.pd"
// substring(start, end) - similar to slice but no negative indices
console.log(filePath.substring(7, 10)); // "ada"
// split() - divide string into array
const parts = filePath.split("/");
console.log(parts); // ["", "users", "ada", "documents", "report.pdf"]
// at() - access character by index (supports negative)
console.log(filePath.at(0)); // "/"
console.log(filePath.at(-1)); // "f"Transforming
const input = " Hello, World! ";
// Case transformation
console.log(input.toUpperCase()); // " HELLO, WORLD! "
console.log(input.toLowerCase()); // " hello, world! "
// Trimming whitespace
console.log(input.trim()); // "Hello, World!"
console.log(input.trimStart()); // "Hello, World! "
console.log(input.trimEnd()); // " Hello, World!"
// Replacing
const text = "foo-bar-baz";
console.log(text.replace("-", "_")); // "foo_bar-baz" (first match only)
console.log(text.replaceAll("-", "_")); // "foo_bar_baz" (all matches)
console.log(text.replace(/-/g, "_")); // "foo_bar_baz" (regex global)
// Padding
const orderNum = "42";
console.log(orderNum.padStart(6, "0")); // "000042"
console.log(orderNum.padEnd(6, ".")); // "42...."
// Repeating
console.log("=".repeat(30)); // "=============================="String Methods Reference Table
| Method | Purpose | Returns |
|---|---|---|
includes(str) | Check if substring exists | Boolean |
startsWith(str) | Check if string starts with value | Boolean |
endsWith(str) | Check if string ends with value | Boolean |
indexOf(str) | Find first occurrence index | Number (-1 if not found) |
slice(start, end) | Extract substring | String |
split(separator) | Split into array | Array |
replace(old, new) | Replace first match | String |
replaceAll(old, new) | Replace all matches | String |
trim() | Remove whitespace from both ends | String |
toLowerCase() | Convert to lowercase | String |
toUpperCase() | Convert to uppercase | String |
padStart(len, char) | Pad from start to target length | String |
repeat(count) | Repeat string n times | String |
at(index) | Get character at index (supports negative) | String |
Tagged Templates
Tagged templates are an advanced feature where a function processes a template literal. The function receives the string parts and interpolated values separately, letting you transform or validate the output.
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] !== undefined ? `**${values[i]}**` : "";
return result + str + value;
}, "");
}
const name = "Ada";
const role = "engineer";
console.log(highlight`Welcome, ${name}! Your role is ${role}.`);
// "Welcome, **Ada**! Your role is **engineer**."Practical: SQL Query Builder
Tagged templates shine for building safe queries that prevent injection:
function sql(strings, ...values) {
const query = strings.reduce((result, str, i) => {
return result + str + (i < values.length ? `$${i + 1}` : "");
}, "");
return {
text: query,
values: values,
};
}
const userId = 42;
const status = "active";
const query = sql`SELECT * FROM users WHERE id = ${userId} AND status = ${status}`;
console.log(query.text); // "SELECT * FROM users WHERE id = $1 AND status = $2"
console.log(query.values); // [42, "active"]Practical: HTML Escaping
function safeHTML(strings, ...values) {
const escape = (str) =>
String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
return strings.reduce((result, str, i) => {
return result + str + (i < values.length ? escape(values[i]) : "");
}, "");
}
const userInput = '<script>alert("xss")</script>';
const safe = safeHTML`<div>${userInput}</div>`;
console.log(safe);
// "<div><script>alert("xss")</script></div>"Best Practices
Modern String Patterns
These practices reflect how strings are handled in modern JavaScript codebases.
Use template literals for any string that includes variables. The ${} syntax is more readable than concatenation and automatically handles type conversion. Reserve single/double quotes for plain strings with no dynamic content.
Always use replaceAll() or regex with /g for global replacements. The replace() method only replaces the first match by default. This is a common source of bugs when developers expect all occurrences to be replaced.
Use includes() instead of indexOf() for existence checks. str.includes("search") returns a boolean, which is more readable than str.indexOf("search") !== -1.
Prefer slice() over substring(). Both extract substrings, but slice() supports negative indices (counting from the end), making it more versatile. slice(-4) gets the last 4 characters.
Use trim() on all user input. Leading and trailing whitespace in form inputs causes matching failures and display issues. Always trim before processing or storing.
Common Mistakes and How to Avoid Them
String Pitfalls
These mistakes appear frequently in JavaScript code and cause subtle display or comparison bugs.
Forgetting that strings are immutable. Every string method returns a NEW string. The original is never modified. Writing name.toUpperCase() without assigning the result is a no-op.
let name = "ada";
name.toUpperCase(); // Returns "ADA" but name is still "ada"
name = name.toUpperCase(); // Now name is "ADA"Using replace() and expecting all matches to be replaced. This is one of the most common JavaScript bugs. Always use replaceAll() or the regex global flag.
Building strings with + in loops. Each concatenation creates a new string. For large loops, use an array and join() instead.
Comparing strings without normalizing case. "Admin" !== "admin". Always convert both sides to the same case before comparing: a.toLowerCase() === b.toLowerCase().
Confusing template literal backticks with single quotes. On many keyboards, backticks (`) are next to the 1 key. Using the wrong character produces a plain string without interpolation support.
Next Steps
Master multi-line strings
Learn advanced multi-line string techniques including indentation handling, HTML templates, and the dedent pattern.
Explore [JavaScript operators](/tutorials/programming-languages/javascript/js-operators-arithmetic-logical-comparison)
See how string operators interact with other data types and how template literals simplify type conversion in expressions.
Learn regular expressions
Pair your string knowledge with regex for powerful text pattern matching, validation, and transformation.
Build a template engine
Create a small template engine that uses tagged templates to render dynamic HTML with automatic escaping and conditional blocks.
Rune AI
Key Insights
- Template literals use backticks: they support interpolation (
${}), multi-line strings, and tagged templates - Interpolation accepts any expression: variables, function calls, ternary operators, and arithmetic all work inside
${} - Strings are immutable: every string method returns a new string and never modifies the original
- Use replaceAll() for global replacements:
replace()only handles the first match by default - Tagged templates enable safe string building: use them for SQL queries, HTML escaping, and internationalization
Frequently Asked Questions
What are template literals in JavaScript?
What is the difference between template literals and regular strings?
When should I use template literals vs string concatenation?
Can I nest template literals in JavaScript?
What are tagged templates and when would I use them?
Conclusion
Template literals are the modern standard for string building in JavaScript, replacing concatenation with readable ${} interpolation, enabling multi-line strings without escape characters, and powering advanced patterns through tagged templates. Combined with JavaScript's string methods like includes(), slice(), replaceAll(), and trim(), you have a complete toolkit for every text operation. The most impactful habit is simple: use backticks for any string that includes variables and regular quotes for everything else.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.