Template Literals & Tagged Templates Interview Questions & Answers
30 questions Updated 2026-06-18
JavaScript template literal interview questions — interpolation, multiline strings, expression embedding, tagged templates, String.raw and security pitfalls around escaping.
Read the in-depth guideJavaScript Template Literals & Tagged Templates — Interpolation, Multiline and DSLsA template literal is a string written with backticks (`)
that supports interpolation and spans multiple lines.
const name = 'Ada'
const msg = `Hello, ${name}!` // "Hello, Ada!"
Backticks replace the need for string concatenation and make embedded
values far more readable than 'Hello, ' + name + '!'.
Any expression inside ${ } is evaluated and its result coerced to a
string, then spliced into place.
const a = 5, b = 3
`Sum is ${a + b}` // "Sum is 8"
`Upper: ${name.toUpperCase()}` // method calls allowed
It's a full expression slot — arithmetic, calls, ternaries, even nested
template literals all work. It is not a statement slot, so if/for
can't go there.
Newlines inside backticks are preserved literally — no \n or
concatenation needed.
const text = `Line 1
Line 2` // contains a real newline
Pitfall: indentation inside the literal becomes part of the string. Leading spaces on the second line are included, which can surprise you when the code is nested deep in a function.
Any valid JavaScript expression: arithmetic, function calls, ternaries, property access, even another template literal.
`Status: ${active ? 'on' : 'off'}`
`Total: ${items.reduce((s, i) => s + i.price, 0)}` //
Keep them simple, though — heavy logic inside ${} hurts readability.
Compute into a variable first when the expression gets complex.
Yes — a ${} slot can contain another backtick string, which is useful for
conditional fragments.
const html = `<ul>${items.map(i => `<li>${i}</li>`).join('')}</ul>`
It works, but deeply nested templates become hard to read. For non-trivial markup, prefer a templating library or break the pieces into named variables.
A tagged template calls a function with the literal's parts. The tag function receives the array of string segments first, then each interpolated value as subsequent arguments.
function tag(strings, ...values) {
return { strings, values }
}
tag`Hi ${name}, you are ${age}`
// strings: ['Hi ', ', you are ', ''], values: [name, age]
The tag fully controls the output — it doesn't have to return a string at all. This powers libraries like styled-components and graphql-tag.
Because the string segments surround the interpolations — there's
always a piece before the first ${} and after the last one (possibly
empty).
tag`a${1}b${2}c`
// strings: ['a', 'b', 'c'] (3)
// values: [1, 2] (2) strings.length === values.length + 1
Tag functions rely on this invariant to interleave: zip
strings[i] with values[i] and append the final strings[n].
String.raw is a built-in tag that returns the string with escape
sequences uninterpreted — backslashes stay literal.
String.raw`C:\new\test` // 'C:\\new\\test' (backslashes kept)
`C:\new\test` // 'C:' + newline + 'ew' + tab + 'est'
Great for Windows paths, regex source, and LaTeX. The strings.raw
property is how any custom tag accesses the un-escaped segments.
Inside a tag, strings also carries a raw array holding the
segments before escape processing — so the tag can choose cooked or raw
text.
function show(strings) {
return strings.raw[0] // raw, e.g. '\\n' stays as backslash-n
}
show`\n` // '\\n' not an actual newline
String.raw is literally implemented by joining strings.raw with the
values.
A tag can rebuild the message from its parts, looking up a translated
template by the static strings segments and re-inserting the dynamic
values.
function i18n(strings, ...values) {
const key = strings.join('{}') // stable lookup key
const translated = dictionary[key] ?? key
return interpolate(translated, values) // locale-aware
}
i18n`Hello ${name}`
Because the static parts are constant per call site, they make a reliable translation key.
The tag escapes the interpolated values while leaving the static
strings (trusted, author-written) untouched — exactly what raw template
literals fail to do.
function safeHtml(strings, ...values) {
return strings.reduce((out, s, i) =>
out + s + (i < values.length ? escapeHtml(values[i]) : ''), '')
}
safeHtml`<p>${userInput}</p>` // userInput is escaped
This is the secure pattern; plain `<p>${userInput}</p>` injects raw
user input.
They perform no escaping — interpolated values go in verbatim. Building HTML or SQL by interpolation invites XSS/SQL injection.
el.innerHTML = `<div>${userInput}</div>` // XSS if userInput has <script>
db.query(`SELECT * FROM u WHERE id = ${id}`) // SQL injection
Use a sanitizing tag for HTML, and parameterized queries for SQL — never interpolate untrusted data into markup or queries directly.
They're more readable, avoid + clutter and stray-space bugs, and handle
multiline naturally.
'Hi ' + name + ', you have ' + n + ' items' // noisy
`Hi ${name}, you have ${n} items` // clear
Concatenation also silently coerces with +, where a misplaced operand can
produce NaN or "[object Object]"; template slots make intent explicit.
Each ${} result is converted with the abstract ToString — objects call
toString(), arrays join with commas, null/undefined stringify
literally.
`${[1, 2, 3]}` // '1,2,3'
`${ {a: 1} }` // '[object Object]' rarely what you want
`${null}` // 'null'
For objects you usually want JSON.stringify(obj) inside the slot rather
than relying on the default [object Object].
Escape them with a backslash.
`A backtick: \`` // "A backtick: `"
`Not a slot: \${x}` // "Not a slot: ${x}" literal
Without the backslash, ${x} would be interpreted as interpolation. This
matters when generating template-literal source code or documentation.
Libraries like styled-components expose a tag that turns the literal into a styled component, injecting interpolated values as dynamic style props.
const Button = styled.button`
color: ${props => props.primary ? 'white' : 'black'};
` // tag processes the CSS + functions
The tag receives the CSS chunks and the prop-accessor functions, then generates real stylesheets at render time.
The gql tag parses the query string into an AST at definition time,
catching syntax errors early and enabling tooling (highlighting, type
generation) on the embedded query.
const QUERY = gql`
query { user { id name } }
` // parsed, not just a string
It also lets fragments be interpolated and composed via ${} while keeping
a single parse step.
Yes — for a given tagged-template call site, the engine reuses the
same frozen strings array across invocations. This lets tags memoize
by identity.
function tag(strings) { /* strings is the same object each call */ }
function run() { return tag`hello` }
run() ; run() // both calls receive the identical strings reference
Libraries exploit this to cache parsed templates (e.g. compiled CSS or
GraphQL) keyed on the strings object.
The literal preserves source indentation, so nested code leaks leading spaces. Use a dedent tag or post-process with a regex.
const sql = `
SELECT *
FROM users
` // each line has leading spaces
const clean = sql.replace(/^\s+/gm, '') // strip leading whitespace
Dedent helper tags are common in test fixtures and embedded SQL/GraphQL to keep output clean while keeping source readable.
Use a ternary or short-circuit expression in the slot — statements aren't allowed.
`Cart${count !== 1 ? 's' : ''}` // pluralize
`${error ? `Error: ${error}` : 'OK'}` // nested + conditional
For anything beyond a simple ternary, compute the fragment in a variable first to keep the literal readable.
Map to strings and join — relying on default array coercion adds
commas you usually don't want.
`<ul>${items.map(i => `<li>${i}</li>`).join('')}</ul>` //
`<ul>${items}</ul>` // inserts commas between items
The explicit .join('') (or .join(', ')) controls the separator instead
of getting the implicit comma.
No — there's no %d/%s formatting. You format inside the slot with
methods or Intl.
`Price: ${value.toFixed(2)}` // 2 decimals
`Date: ${new Intl.DateTimeFormat('en').format(d)}` // locale format
For padding/number formatting use padStart, toLocaleString, or Intl.*
rather than expecting printf-style directives.
${} with nothing is a SyntaxError — a slot must contain an
expression. Whitespace alone is also invalid.
`${}` // SyntaxError
`${ '' }` // an empty-string expression is fine
`${ /* x */ undefined }` // valid, inserts 'undefined'
You need a real expression; if you want nothing, interpolate an empty
string ''.
Yes — the tag's return value is whatever the function returns. It can be an object, a component, a parsed AST, a function, anything.
function obj(strings, ...values) {
return { template: strings, data: values } // returns an object
}
const x = obj`a ${1}` // x is an object, not a string
This flexibility is the whole point — tags are a DSL hook, not just string formatters.
End a line with a backslash to continue without inserting a newline.
const s = `This is one long \
logical line` // no newline between 'long ' and 'logical'
Be careful: any spaces after the backslash break it. This is occasionally handy, but joining string parts or using normal wrapping is usually clearer.
Yes — slots are expressions, so calls (including IIFEs) are allowed and their return value is interpolated.
`Now: ${getTime()}`
`Result: ${(() => compute())()}` // IIFE result
Side effects run at interpolation time, in left-to-right order with the other slots — keep that in mind if a slot mutates state.
For locale-aware numbers/dates, complex pluralization (ICU MessageFormat),
or printf-style alignment, dedicated libraries (Intl, messageformat) do
what raw templates can't.
// pluralization rules vary by language — a template can't express them all
messageFormat.format({ count }) // handles one/few/many per locale
Templates excel at simple interpolation and embedded DSLs; reach for formatting libs when human-language or numeric formatting gets involved.
No — JSX is a separate compile-time syntax transformed by Babel/TypeScript,
not a runtime template literal. But tagged templates (html\...``) are
the JSX-free alternative used by libraries like lit-html.
// lit-html: tagged template, no build step needed
html`<div>${content}</div>` // runtime, parsed by the html tag
So tagged templates give a "JSX-like" templating experience without a compiler.
Whatever sits between ${} slots in the source is kept exactly, so you
manage spacing by what you type — or strip it in a tag.
`${a} ${b}` // single space between
`${a}${b}` // no space
`${a}
${b}` // newline preserved between them
For HTML where whitespace collapses anyway it rarely matters; for precise output (CSV, fixed-width), a normalizing tag gives full control.
Keep ${} expressions simple, never interpolate untrusted data into
HTML/SQL without escaping/parameters, use String.raw for paths/regex, and
mind leaked indentation in multiline strings.
const label = `${count} ${count === 1 ? 'item' : 'items'}` // simple
el.textContent = `Hello ${name}` // textContent, not innerHTML
Prefer textContent over innerHTML, and a sanitizing tag when raw markup
is unavoidable.
Practice tests are coming soon
Get notified when interactive mock interviews and quizzes launch.