What Are Template Literals?
Template literals are string literals that allow embedded expressions, multiline strings, and special constructs called tagged templates. Introduced in ES6 (ES2015), they use backticks (`) instead of single or double quotes.
// Old way — quotes
const greeting = 'Hello, ' + name + '!';
// New way — template literal
const greeting = `Hello, ${name}!`;
This might look like a small change, but template literals unlock capabilities that plain strings simply cannot match. By the end of this guide you will know how to use them alongside JavaScript's full arsenal of string methods.
Template Literal Syntax
Basic Usage
Wrap your string in backticks instead of quotes.
const simple = `This is a template literal`;
const withQuotes = `She said "hello" and he said 'hi'`;
console.log(simple); // This is a template literal
console.log(withQuotes); // She said "hello" and he said 'hi'
Notice that you can freely use both single and double quotes inside backticks without escaping.
Multiline Strings
Before template literals, multiline strings required concatenation or escape characters.
// Old way — concatenation
const old = 'Line one\n' +
'Line two\n' +
'Line three';
// Old way — escape
const escaped = 'Line one\n\
Line two\n\
Line three';
// Template literal — just press Enter
const modern = `Line one
Line two
Line three`;
console.log(modern);
// Line one
// Line two
// Line three
The whitespace is preserved exactly as you type it. This makes template literals perfect for generating HTML, SQL queries, or any structured text.
function createCard(title, body) {
return `
<div class="card">
<h2 class="card-title">${title}</h2>
<p class="card-body">${body}</p>
</div>
`;
}
console.log(createCard('Welcome', 'Thanks for visiting'));
Watch out: the leading whitespace in each line becomes part of the string. If indentation matters (like in a <pre> block), you may need to trim it.
String Interpolation
The ${} syntax lets you embed any JavaScript expression inside a template literal.
Variables and Expressions
const name = 'Alice';
const age = 30;
// Variable interpolation
console.log(`Name: ${name}`); // Name: Alice
// Arithmetic
console.log(`Next year: ${age + 1}`); // Next year: 31
// Ternary
console.log(`Status: ${age >= 18 ? 'adult' : 'minor'}`); // Status: adult
// Function calls
console.log(`Upper: ${name.toUpperCase()}`); // Upper: ALICE
Complex Expressions
Anything that returns a value works inside ${}.
const items = ['apple', 'banana', 'cherry'];
// Array method
const list = `Fruits: ${items.join(', ')}`;
console.log(list); // Fruits: apple, banana, cherry
// Object property access
const user = { first: 'John', last: 'Doe' };
console.log(`Full name: ${user.first} ${user.last}`); // Full name: John Doe
// Nested template literals
const table = `
| Name | Score |
|---------|-------|
${[
{ name: 'Alice', score: 95 },
{ name: 'Bob', score: 87 },
].map(r => `| ${r.name.padEnd(7)} | ${String(r.score).padEnd(5)} |`).join('\n')}
`;
console.log(table);
Expression Evaluation Order
Expressions are evaluated left to right, and their results are converted to strings using the default toString() method.
const obj = {
toString() {
return 'custom string';
}
};
console.log(`Object: ${obj}`); // Object: custom string
// Arrays call join(',') internally
console.log(`Array: ${[1, 2, 3]}`); // Array: 1,2,3
// Objects without custom toString
console.log(`Plain: ${{ a: 1 }}`); // Plain: [object Object]
Tagged Templates
Tagged templates are an advanced feature. A tag is a function that processes a template literal, giving you full control over how the string is assembled.
How Tags Work
A tag function receives the string parts as an array, followed by each interpolated value as separate arguments.
function highlight(strings, ...values) {
console.log('Strings:', strings);
console.log('Values:', values);
return strings.reduce((result, str, i) => {
return result + str + (values[i] !== undefined ? `<mark>${values[i]}</mark>` : '');
}, '');
}
const name = 'Alice';
const role = 'admin';
const output = highlight`User ${name} has role ${role}`;
console.log(output);
// Strings: ['User ', ' has role ', '']
// Values: ['Alice', 'admin']
// User <mark>Alice</mark> has role <mark>admin</mark>
The flow looks like this:
Template: `User ${name} has role ${role}`
strings[0] strings[1] strings[2]
v v v
Splits into: "User " | " has role " | ""
^ ^
values[0] values[1]
"Alice" "admin"
Practical Tagged Template: HTML Escaping
Prevent XSS attacks by escaping user input automatically.
function safeHTML(strings, ...values) {
const escapeHTML = (str) =>
String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
return strings.reduce((result, str, i) => {
const value = values[i] !== undefined ? escapeHTML(values[i]) : '';
return result + str + value;
}, '');
}
const userInput = '<script>alert("xss")</script>';
const safe = safeHTML`<div>${userInput}</div>`;
console.log(safe);
// <div><script>alert("xss")</script></div>
Practical Tagged Template: SQL Query Builder
function sql(strings, ...values) {
const params = [];
const query = strings.reduce((result, str, i) => {
if (i < values.length) {
params.push(values[i]);
return result + str + `$${params.length}`;
}
return result + str;
}, '');
return { query, params };
}
const name = 'Alice';
const age = 30;
const result = sql`SELECT * FROM users WHERE name = ${name} AND age > ${age}`;
console.log(result.query);
// SELECT * FROM users WHERE name = $1 AND age > $2
console.log(result.params);
// ['Alice', 30]
String.raw
JavaScript provides a built-in tag called String.raw that returns the raw string without processing escape sequences.
// Normal template literal processes \n as newline
console.log(`Line1\nLine2`);
// Line1
// Line2
// String.raw keeps the literal characters
console.log(String.raw`Line1\nLine2`);
// Line1\nLine2
// Useful for Windows paths
const path = String.raw`C:\Users\alice\Documents`;
console.log(path); // C:\Users\alice\Documents
// Useful for regex patterns
const pattern = String.raw`\d+\.\d+`;
console.log(pattern); // \d+\.\d+
Essential String Methods
JavaScript strings come with a rich set of built-in methods. Let's cover the ones you will use most often.
Searching Within Strings
const sentence = 'The quick brown fox jumps over the lazy dog';
// includes — returns boolean
sentence.includes('fox'); // true
sentence.includes('cat'); // false
sentence.includes('Fox'); // false (case-sensitive)
// startsWith / endsWith
sentence.startsWith('The'); // true
sentence.endsWith('dog'); // true
sentence.endsWith('Dog'); // false
// indexOf — returns position or -1
sentence.indexOf('fox'); // 16
sentence.indexOf('cat'); // -1
// lastIndexOf — searches from end
const text = 'abcabc';
text.lastIndexOf('abc'); // 3
Extracting Substrings
Three methods extract parts of a string. Here is a comparison:
Method Arguments Negative indices?
──────────────────────────────────────────────────────────
slice(start, end) start and end positions Yes
substring(start, end) start and end positions No (treats as 0)
substr(start, length) start position + length Yes (deprecated)
const str = 'JavaScript';
// 0123456789
// slice — preferred method
str.slice(0, 4); // 'Java'
str.slice(4); // 'Script'
str.slice(-6); // 'Script'
str.slice(-6, -3); // 'Scr'
// substring — similar but no negative indices
str.substring(0, 4); // 'Java'
str.substring(4, 0); // 'Java' (auto-swaps if start > end)
// substr — deprecated, avoid
str.substr(4, 6); // 'Script' (start, length)
Recommendation: Use slice() everywhere. It handles negative indices, behaves predictably, and works on both strings and arrays.
split — Breaking Strings Apart
const csv = 'apple,banana,cherry,date';
// Split by comma
csv.split(','); // ['apple', 'banana', 'cherry', 'date']
// Split by comma with limit
csv.split(',', 2); // ['apple', 'banana']
// Split every character
'hello'.split(''); // ['h', 'e', 'l', 'l', 'o']
// Split by whitespace
'one two three'.split(/\s+/); // ['one', 'two', 'three']
// Split by multiple delimiters
'a-b_c.d'.split(/[-_.]/); // ['a', 'b', 'c', 'd']
replace and replaceAll
const text = 'foo bar foo baz foo';
// replace — first occurrence only
text.replace('foo', 'qux');
// 'qux bar foo baz foo'
// replace with regex + global flag — all occurrences
text.replace(/foo/g, 'qux');
// 'qux bar qux baz qux'
// replaceAll — all occurrences (ES2021)
text.replaceAll('foo', 'qux');
// 'qux bar qux baz qux'
// replace with callback function
'hello world'.replace(/\b\w/g, (char) => char.toUpperCase());
// 'Hello World'
Trimming and Padding
// Trimming whitespace
const messy = ' hello world ';
messy.trim(); // 'hello world'
messy.trimStart(); // 'hello world '
messy.trimEnd(); // ' hello world'
// Padding — pad to target length
'5'.padStart(3, '0'); // '005'
'42'.padStart(5, ' '); // ' 42'
'hi'.padEnd(10, '.'); // 'hi........'
'abc'.padEnd(6, '12'); // 'abc121'
// Practical: format a table
const data = [
{ name: 'Alice', score: 95 },
{ name: 'Bob', score: 8 },
{ name: 'Charlie', score: 100 },
];
data.forEach(({ name, score }) => {
console.log(`${name.padEnd(10)} ${String(score).padStart(5)}`);
});
// Alice 95
// Bob 8
// Charlie 100
repeat
'ha'.repeat(3); // 'hahaha'
'-'.repeat(40); // '----------------------------------------'
'abc'.repeat(0); // ''
// Build a simple progress bar
function progressBar(percent) {
const filled = Math.round(percent / 5);
const empty = 20 - filled;
return `[${'#'.repeat(filled)}${'-'.repeat(empty)}] ${percent}%`;
}
console.log(progressBar(75));
// [###############-----] 75%
Case Conversion and Character Access
const str = 'Hello World';
str.toUpperCase(); // 'HELLO WORLD'
str.toLowerCase(); // 'hello world'
// Character access
str.charAt(0); // 'H'
str[0]; // 'H' (bracket notation)
str.charCodeAt(0); // 72 (Unicode code point)
str.codePointAt(0); // 72 (handles surrogate pairs)
// at() — supports negative indices (ES2022)
str.at(0); // 'H'
str.at(-1); // 'd'
str.at(-5); // 'W'
Regular Expressions with Strings
Regular expressions are patterns used to match character combinations. They pair with string methods to create powerful text processing tools.
Creating a RegExp
// Literal syntax (preferred)
const pattern1 = /hello/;
const pattern2 = /hello/gi; // g = global, i = case-insensitive
// Constructor syntax (when pattern is dynamic)
const searchTerm = 'hello';
const pattern3 = new RegExp(searchTerm, 'gi');
Common RegExp Patterns
Pattern Meaning Example Match
─────────────────────────────────────────────────────
\d any digit "7"
\D any non-digit "a"
\w word character [a-zA-Z0-9_] "x"
\W non-word character "!"
\s whitespace " "
\S non-whitespace "a"
. any character (except \n) "z"
^ start of string ^Hello
$ end of string world$
+ one or more \d+
* zero or more \w*
? zero or one colou?r
{n} exactly n \d{3}
{n,m} between n and m \d{2,4}
[abc] character class [aeiou]
[^abc] negated class [^0-9]
(abc) capture group (\d+)-(\d+)
(?:abc) non-capture group (?:Mr|Mrs)
a|b alternation cat|dog
test — Check for a Match
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
emailPattern.test('[email protected]'); // true
emailPattern.test('not-an-email'); // false
emailPattern.test('[email protected]'); // true
const phonePattern = /^\d{3}-\d{3}-\d{4}$/;
phonePattern.test('555-123-4567'); // true
phonePattern.test('5551234567'); // false
match — Extract Matches
const text = 'Call 555-1234 or 555-5678 for info';
// Without global flag — returns first match with details
const single = text.match(/\d{3}-\d{4}/);
console.log(single[0]); // '555-1234'
console.log(single.index); // 5
// With global flag — returns all matches as array
const all = text.match(/\d{3}-\d{4}/g);
console.log(all); // ['555-1234', '555-5678']
// Named capture groups
const dateStr = '2026-03-07';
const dateMatch = dateStr.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
console.log(dateMatch.groups);
// { year: '2026', month: '03', day: '07' }
matchAll — Iterate Over All Matches (ES2020)
const text = 'Price: $10.99, Discount: $2.50, Total: $8.49';
const pricePattern = /\$(\d+\.\d{2})/g;
for (const match of text.matchAll(pricePattern)) {
console.log(`Found ${match[0]} at index ${match.index}, value: ${match[1]}`);
}
// Found $10.99 at index 7, value: 10.99
// Found $2.50 at index 25, value: 2.50
// Found $8.49 at index 42, value: 8.49
replace with RegExp
// Remove all non-alphanumeric characters
'Hello, World! 123'.replace(/[^a-zA-Z0-9]/g, '');
// 'HelloWorld123'
// Swap first and last name
'Doe, John'.replace(/(\w+), (\w+)/, '$2 $1');
// 'John Doe'
// Mask credit card number
'4111222233334444'.replace(/(\d{4})(\d{8})(\d{4})/, '$1********$3');
// '4111********4444'
// Convert camelCase to kebab-case
'backgroundColor'.replace(/([A-Z])/g, '-$1').toLowerCase();
// 'background-color'
// Replace with function
'2026-03-07'.replace(
/(\d{4})-(\d{2})-(\d{2})/,
(_, year, month, day) => `${day}/${month}/${year}`
);
// '07/03/2026'
Practical Patterns
Building a Simple Template Engine
function render(template, data) {
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
return key in data ? data[key] : `{{${key}}}`;
});
}
const template = `Hello {{name}}, welcome to {{site}}!
Your role is {{role}}.`;
const result = render(template, {
name: 'Alice',
site: 'DevsOfUs',
});
console.log(result);
// Hello Alice, welcome to DevsOfUs!
// Your role is {{role}}.
Slugify a String
function slugify(text) {
return text
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '') // remove non-word chars (except spaces and hyphens)
.replace(/\s+/g, '-') // replace spaces with hyphens
.replace(/-+/g, '-'); // collapse multiple hyphens
}
slugify('Hello World! This is a Test');
// 'hello-world-this-is-a-test'
slugify(' Template Literals & String Methods ');
// 'template-literals--string-methods'
Validate and Parse URLs
function parseURL(url) {
const pattern = /^(https?):\/\/([^/:]+)(?::(\d+))?(\/[^?#]*)?(?:\?([^#]*))?(?:#(.*))?$/;
const match = url.match(pattern);
if (!match) return null;
return {
protocol: match[1],
host: match[2],
port: match[3] || null,
path: match[4] || '/',
query: match[5] || null,
hash: match[6] || null,
};
}
console.log(parseURL('https://example.com:8080/path?q=hello#section'));
// {
// protocol: 'https',
// host: 'example.com',
// port: '8080',
// path: '/path',
// query: 'q=hello',
// hash: 'section'
// }
Multiline String Processing
const logData = `
2026-03-07 10:00:00 INFO Server started
2026-03-07 10:00:05 WARN High memory usage
2026-03-07 10:00:10 ERROR Database connection failed
2026-03-07 10:00:15 INFO Retry successful
`;
// Parse log entries
const entries = logData
.trim()
.split('\n')
.map(line => {
const match = line.match(/^(\S+ \S+)\s+(INFO|WARN|ERROR)\s+(.+)$/);
if (!match) return null;
return { timestamp: match[1], level: match[2], message: match[3] };
})
.filter(Boolean);
// Filter errors only
const errors = entries.filter(e => e.level === 'ERROR');
console.log(errors);
// [{ timestamp: '2026-03-07 10:00:10', level: 'ERROR', message: 'Database connection failed' }]
Method Chaining with Strings
Since most string methods return new strings, you can chain them for concise transformations.
const input = ' Hello, WORLD! This is A test. ';
const result = input
.trim() // 'Hello, WORLD! This is A test.'
.toLowerCase() // 'hello, world! this is a test.'
.replace(/[^a-z\s]/g, '') // 'hello world this is a test'
.split(/\s+/) // ['hello', 'world', 'this', 'is', 'a', 'test']
.map(w => w[0].toUpperCase() + w.slice(1)) // ['Hello', 'World', 'This', 'Is', 'A', 'Test']
.join(' '); // 'Hello World This Is A Test'
console.log(result); // Hello World This Is A Test
String Immutability
Strings in JavaScript are immutable. Every string method returns a new string; the original is never modified.
const original = 'hello';
const upper = original.toUpperCase();
console.log(original); // 'hello' (unchanged)
console.log(upper); // 'HELLO' (new string)
// You cannot change individual characters
original[0] = 'H'; // silently fails (or throws in strict mode)
console.log(original); // 'hello'
This is why string operations can be expensive when done repeatedly in a loop. For heavy string building, consider using an array and joining at the end.
// Slow — creates many intermediate strings
let result = '';
for (let i = 0; i < 10000; i++) {
result += `item ${i}, `;
}
// Fast — builds array, joins once
const parts = [];
for (let i = 0; i < 10000; i++) {
parts.push(`item ${i}`);
}
const result = parts.join(', ');
Unicode and Special Characters
Template literals handle Unicode just like regular strings, but they pair well with modern Unicode-aware methods.
// Emoji and unicode in template literals
const emoji = `Hello ${'world'} \u{1F600}`;
console.log(emoji); // Hello world (grinning face emoji)
// String length vs actual characters
const face = '\u{1F600}';
console.log(face.length); // 2 (surrogate pair)
console.log([...face].length); // 1 (actual character count)
// Normalize unicode
const a1 = '\u00e9'; // e with acute (single code point)
const a2 = 'e\u0301'; // e + combining acute accent
console.log(a1 === a2); // false
console.log(a1.normalize() === a2.normalize()); // true
Quick Reference Table
Method Returns Purpose
─────────────────────────────────────────────────────────────
str.includes(s) boolean contains substring?
str.startsWith(s) boolean starts with substring?
str.endsWith(s) boolean ends with substring?
str.indexOf(s) number position of first match (-1 if none)
str.slice(a, b) string extract from a to b
str.split(sep) array break into parts
str.replace(a, b) string replace first/all matches
str.replaceAll(a,b) string replace all matches
str.trim() string remove leading/trailing whitespace
str.padStart(n, c) string pad from start to length n
str.padEnd(n, c) string pad from end to length n
str.repeat(n) string repeat n times
str.toUpperCase() string all uppercase
str.toLowerCase() string all lowercase
str.at(i) string character at index (neg ok)
str.match(rx) array find regex matches
str.matchAll(rx) iterator iterate all regex matches
/rx/.test(str) boolean test if regex matches
Key Takeaways
- Template literals use backticks and support multiline strings, interpolation with
${}, and tagged templates - Tagged templates give you full control over string assembly — great for HTML escaping, SQL parameterization, and DSLs
- Use
slice()for substring extraction — it is the most predictable and versatile option split()andjoin()are inverse operations — use them together for text transformationsreplace()with a regex and thegflag (orreplaceAll()) handles global replacements- Regular expressions pair with
test(),match(),matchAll(), andreplace()for pattern-based text processing - Strings are immutable — every operation returns a new string
- For heavy string building, use an array +
join()instead of repeated concatenation padStart()andpadEnd()are perfect for formatting tabular output- Use
String.rawwhen you need literal backslashes (file paths, regex patterns)