ES6+advanced
Tagged Template Literals for Safe HTML
Using tagged template functions to automatically escape interpolated values, a real-world defense against injection when building HTML or SQL strings dynamically.
A tag function receives the literal parts and the interpolated values separately. That separation is what makes escaping possible.
A safe-HTML tag function is like a print shop that always runs your hand-written text through a content checker before merging it into a pre-approved letterhead template, but leaves the letterhead itself untouched.
Key Concepts
1
function html(strings, ...values) {
return strings.reduce((out, s, i) => {
const v = i < values.length ? escapeHtml(values[i]) : '';
return out + s + v;
}, '');
}2
const safe = html`<p>${userInput}</p>`;<p>${userInput}</p>
3
Why plain interpolation cannot do this.
const bad = `<p>${userInput}</p>`;<p>${userInput}</p>
4
By the time the string exists, the value and the markup are indistinguishable. If userInput contains
<script>, it is now part of the HTML.
userInput<script>
5
The tag sees them apart: strings is what the developer wrote, values is what came from outside. Only the
second needs escaping.
stringsvalues
6
The escape function.
const escapeHtml = (s) => String(s).replace(/[&<>"']/g, (c) => ({
'&': '&', '<': '<', '>': '>',
'"': '"', "'": '''
}[c]));7
The same idea for SQL, and here it does more than escape:
8
function sql(strings, ...values) {
return {
text: strings.reduce((q, s, i) =>
q + s + (i < values.length ? `$${i + 1}` : ''), ''),
values
};
}$${i + 1}
9
const q = sql`SELECT * FROM users WHERE id = ${id}`;
// { text: 'SELECT * FROM users WHERE id = $1', values: [id] }SELECT * FROM users WHERE id = ${id}
10
The value never enters the SQL text at all — it becomes a bound parameter, which is the only reliable
defence against injection.
11
Where you meet this pattern. lit-html for templating, styled-components for CSS, gql for GraphQL,
and parameterised query builders.
lit-htmlstyled-componentsgql
12
The caution. A tag can only escape what it is given. Interpolating into an attribute or a URL needs
different escaping than into text content:
13
html`<a href="${url}">` needs URL escaping, not HTML
html`<div onclick="${x}">` unsafe in any escaping<a href="${url}"><div onclick="${x}">
14
Libraries that do this properly are context-aware. A hand-written tag that escapes everything the same way
is better than nothing and is not a complete defence.
15
And the underlying point. The reason this works is that the tag can distinguish trusted from untrusted
input structurally. Any escaping applied after concatenation is guesswork.
16
What the interviewer is probing.1. "How does a tag make interpolation safe?" Probing: the separation. Stalls: "It escapes
everything." Moves up: the tag receives static chunks and values separately, so it can escape only
the values — the static markup is trusted by construction.
17
2. "Why is this better than escaping manually?" Probing: the failure mode. Stalls: "It is
the same." Moves up: manual escaping is easy to forget on one interpolation; the tag applies it to
every value by default.
18
3. "What else uses this pattern?" Probing: recognition. Stalls: "Nothing." Moves up:
styled-components for CSS, and parameterised SQL helpers that keep values out of the statement text.
19
4. "Does a tag have to return a string?" Probing: the flexibility. Stalls: "Yes." *Moves
up:* no — it can return anything, which is how libraries return DOM nodes or query objects.