Combining text with values is one of the most common tasks in programming. The older approach is string concatenation with the + operator: "Hello, " + name + "!". It works, but it drowns the meaning in quotes, plus signs, and easy-to-miss brackets. One forgotten space after a comma and your output reads like a telegram. Worse, nesting function calls inside concatenation quickly produces a wall of punctuation that is hard to read and harder to modify without breaking. Developers used to reach for array join methods or complex string builders to avoid the mess.
Template literals fix this by letting you write strings that read almost like the sentences you would speak aloud. Wrap the string in backticks and drop any expression inside ${...}. JavaScript evaluates the expression and splices the result straight into the text. You can embed variables, arithmetic, function calls, or even ternary expressions without ever leaving the string. The result is cleaner, safer, and far easier to scan when you come back to the code next month.
flowchart LR
T["backticks wrap it all"] --> P["${ expression }"]
P --> E["evaluated"]
E --> O["spliced into text"]
style P fill:#e0900b,color:#fff
style O fill:#1e7f3d,color:#fff
The anatomy of a template literal
A template literal is a string wrapped in backticks instead of single or double quotes. Any ${...} inside is treated as live code: the expression is evaluated and dropped into the surrounding text. You can put any valid JavaScript expression inside the braces — arithmetic, function calls, ternary operators, or even another template literal.
const price = 4.5;
const qty = 3;
console.log(`Total: $${qty * price}`);
Notice the $$ on the left side: the first $ is a literal dollar sign, and the second $ opens the expression. Template literals do not add or remove whitespace automatically, so if you want a space before a value you must include it inside the backticks. The backticks themselves are not part of the final string. This syntax is sometimes called string interpolation, though the JavaScript specification calls it a template literal because the string can contain multiple lines and tagged processing.
const item = 'coffee';
const qty = 2;
const price = 4.5;
const oldWay = 'Order: ' + qty + ' x ' + item + ' = $' + (qty * price);
const newWay = `Order: ${qty} x ${item} = $${qty * price}`;
console.log(oldWay);
console.log(newWay);
Multi-line strings
Before template literals, a string that spanned multiple lines required escape sequences such as \n or awkward string concatenation. Template literals preserve every newline and space you type inside the backticks, which makes them ideal for formatting simple text blocks, generating markdown, or building HTML fragments. You simply hit Return inside the backticks and the newline becomes part of the string. Multi-line template literals are especially useful when generating SQL queries, JSON payloads, or markdown documentation directly in your JavaScript code.
Be careful with indentation. If you indent the second line to align with your code, those spaces become part of the string. Most code editors do not strip that indentation automatically, so the resulting text can end up with unexpected leading spaces. For simple cases, start the text flush against the left margin; for complex cases, libraries exist to strip common indentation. If you need a line break without starting a new line in the source, you can still use \n inside a template literal, though doing so partly defeats the purpose.
flowchart TD O["concatenation with plus signs"] --> X["awkward and error-prone"] T["backticks with literal line breaks"] --> Y["clean and readable"] style O fill:#b91c1c,color:#fff style T fill:#1e7f3d,color:#fff
const poem = `Roses are red, Violets are blue, Template literals save time for you.`; console.log(poem);
Tagged templates
A tagged template is a template literal preceded by a function name. The function receives two arrays: the first contains the raw string pieces between the expressions, and the second contains the evaluated values. This lets you build custom formatting, sanitization, or validation logic without changing the call site syntax. The caller writes what looks like a normal template literal, but the tag function gets to inspect and transform every piece. Because the tag receives every piece of the string, it can enforce rules such as 'every number must be formatted to two decimal places' or 'every user-supplied string must be HTML-escaped before insertion'.
function currency(strings, ...values) {
return strings.reduce((result, str, i) => {
const val = values[i] ?? '';
return result + str + (typeof val === 'number' ? `$${val.toFixed(2)}` : val);
}, '');
}
const total = currency`Price: ${4.5}, Tax: ${0.38}`;
The tag function is not called like an ordinary function. You write the name immediately followed by the template literal, with no parentheses in between. That syntax is what tells JavaScript to destructure the literal into strings and values and hand them to your function. It is a small syntactic detail with enormous power. Tagged templates power everything from CSS-in-JS libraries to safe HTML escaping, because the tag sees the raw text before any browser interprets it.
flowchart LR I["tag plus template literal"] --> F["tag function"] F --> S["strings array"] F --> V["values array"] S --> O["processed output"] V --> O style F fill:#e0900b,color:#fff style O fill:#1e7f3d,color:#fff
function priceTag(strings, ...values) {
return strings.reduce((out, str, i) => {
const v = values[i];
return out + str + (typeof v === 'number' ? `$${v.toFixed(2)}` : v || '');
}, '');
}
const item = 'coffee';
const cost = 4.5;
console.log(priceTag`Item: ${item}, Cost: ${cost}`);
Raw strings and escaping
Inside a tag function, the strings array has a special property called .raw. It contains the exact text as it was typed, including escape sequences that would normally be processed. For example, a backslash-n inside a template literal is a newline in strings[i], but it is the two characters backslash and n in strings.raw[i]. This is how the built-in String.raw tag works: it returns the string exactly as written, ignoring escape processing.
You rarely need .raw in everyday code, but knowing it exists explains why tag functions can behave differently from ordinary string concatenation. It also means you cannot fake a tagged template by calling the function with arrays you built yourself, because the runtime attaches .raw automatically and only for true template literals.
Escaping backticks and dollars
Because backticks delimit the literal, you cannot include a literal backtick inside a template literal without escaping it with a backslash: \`. The same rule applies to the ${ sequence: if you want a literal dollar sign followed by an opening brace, you must escape the dollar sign as \$. In practice, these situations are rare because template literals are usually used for human-readable text rather than code snippets. When you do need them, the escape rules are consistent with the rest of JavaScript: a backslash removes the special meaning from the character that follows it.
What does this print? Track the expression inside the template literal.
const a = 2;
const b = 3;
console.log(`The sum of ${a} and ${b} is ${a + b}`);
The expressions ${a}, ${b}, and ${a + b} are evaluated and inserted.
a + b equals 5.
Write greeting(name, role) that returns a template literal in exactly this shape: Hello, <name>! You are a <role>. Use backticks and ${...}.
function greeting(name, role) {
// return a template literal
}
Wrap the whole string in backticks.
Use ${name} and ${role} to splice the arguments into the text.
function greeting(name, role) {
return `Hello, ${name}! You are a ${role}.`;
}
This tag function is meant to upper-case every string piece and return them joined together. But the call site uses parentheses instead of backticks, so the function receives a plain string and breaks. Fix the call so it uses tagged template syntax.
function shout(strings) {
return strings.map(s => s.toUpperCase()).join('');
}
const result = shout('hello world');
Remove the parentheses around 'hello world'.
Wrap hello world in backticks directly after the function name.
function shout(strings) {
return strings.map(s => s.toUpperCase()).join('');
}
const result = shout`hello world`;
What does this print? A tagged template receives the string pieces and the evaluated values separately.
function logParts(strings, ...values) {
console.log(strings.length);
console.log(values.length);
}
const x = 10;
logParts`A${x}B`;
For A${x}B, the strings array contains the pieces before and after the value.
values receives the single evaluated expression.
Write stripTag(strings, ...values) that acts as a tagged template. It should join the string pieces with the values, but if a value is a string, trim leading and trailing whitespace from it before inserting. Return the final combined string. So stripTaghello ${' world '}`` returns 'hello world'.
function stripTag(strings, ...values) {
// join pieces, trimming string values
}
Use strings.reduce to rebuild the output.
Check typeof values[i] === 'string' and call .trim() before adding it.
function stripTag(strings, ...values) {
return strings.reduce((result, str, i) => {
const val = values[i];
const cleaned = typeof val === 'string' ? val.trim() : val;
return result + str + (cleaned !== undefined ? cleaned : '');
}, '');
}
Recap
- Template literals wrap strings in backticks and splice expressions with
${...}. - They replace noisy concatenation with readable, sentence-like code.
- Backticks preserve newlines and spaces, making multi-line text natural.
- A tagged template calls a function with the raw strings and evaluated values as separate arrays.
- Tag functions are not normal calls: write the name followed directly by backticks, never parentheses.
- The
.rawproperty on the strings array gives unprocessed escape sequences. - Tagged templates are the foundation of styled-components, internationalization libraries, and HTML sanitizers.
- Mastering template literals early will save you hours of debugging concatenation errors.
Next you will learn to safely access nested properties and provide sensible defaults with optional chaining and nullish coalescing.