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 DSLs

A 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.