Template Literals and Tagged Templates
Backtick strings, interpolation, multiline, raw strings, and tagged templates for DSLs — without inventing XSS.
- javascript
- template-literals
Template literals replace most 'a' + b + 'c' mess with readable interpolation and real multiline strings. Tagged templates pass the parts to a function — the foundation for HTML DSLs, SQL helpers, and i18n libraries.
Basics
const user = 'Ada';
const msg = `Hello, ${user}!`;
const block = `
line 1
line 2
`; // includes newlines and indentation as written
Expressions inside ${} can be any JS; keep them simple for readability.
`Total: ${items.reduce((s, i) => s + i.price, 0).toFixed(2)}`;
Nesting and escaping
const outer = `say ${`hello ${user}`}`;
// backticks inside: \`
const path = `C:\\temp\\file`; // still need escapes for \ and ` and ${
To include a literal ${, escape: \${not interpolated}.
String.raw
String.raw`C:\temp\file`; // backslashes preserved
// useful for regex authoring helpers / Windows paths in demos
Also available as the default tag: String.raw is itself a tag function.
Tagged templates
function tag(strings, ...values) {
console.log(strings); // TemplateStringsArray
console.log(values);
return strings.reduce((s, part, i) => s + part + (values[i] ?? ''), '');
}
tag`a${1}b${2}c`; // strings ['a','b','c'], values [1,2]
strings.raw holds unescaped escapes (like String.raw).
Practical tags
// logging
function debug(strings, ...values) {
return strings.reduce((out, s, i) => {
const v = values[i];
const shown = typeof v === 'object' ? JSON.stringify(v) : v;
return out + s + (shown ?? '');
}, '');
}
console.log(debug`user=${user} count=${n}`);
For HTML, escape values by default — see tagged template sanitization on this site. Never do:
el.innerHTML = `<div>${untrusted}</div>`; // XSS
Performance myth
Template literals are not magically slow; engines optimize them. Don’t micro-optimize back to concat without data. Prefer clarity.
Interview answer (out loud)
“Template literals use backticks, allow ${interpolation} and multiline strings. A tag function before the literal receives the string chunks and values separately — useful for safe HTML, i18n, and DSLs. I still escape untrusted values before any HTML sink.”
Multiline indentation trick
function dedent(strings, ...values) {
// simple: strip common leading whitespace from cooked strings
let raw = strings.reduce((out, s, i) => out + s + (values[i] ?? ''), '');
const lines = raw.replace(/^\n/, '').replace(/\n\s*$/, '').split('\n');
const indents = lines.filter(Boolean).map((l) => l.match(/^ */)[0].length);
const min = Math.min(...indents);
return lines.map((l) => l.slice(min)).join('\n');
}
const sql = dedent`
select id, name
from users
where active = 1
`;
Real dedent libraries handle edge cases; the point is tags enable this class of DX.
Expressions and side effects
`value=${(console.log('runs'), 42)}`; // log runs while building string
Interpolation is eager. Don’t hide network calls inside ${} — keep templates for formatting values you already have.
When concat is clearer
Very short '(' + id + ')' can read better than a template. Prefer templates when there are multiple interpolations or newlines — not as ideology.
Further reading
Related
- Tagged Template Sanitization Idea
- String Methods Worth Knowing
- Regular Expressions Essentials
- Type Coercion Rules
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.