JavaScript · Modern JavaScript (ES6+)

JavaScript Template Literals & Tagged Templates — Interpolation, Multiline and DSLs

6 min read Updated 2026-06-18

Practice Template Literals & Tagged Templates interview questions

Strings, upgraded

Before ES2015, building strings in JavaScript meant clumsy concatenation with + and escaped newlines. Template literals — strings written with backticks — replaced that with clean interpolation, real multiline support, and a powerful extension called tagged templates that turns a string into a mini domain-specific language. They're used everywhere: logging, HTML generation, CSS-in-JS, GraphQL queries, and internationalization. This guide covers the everyday features and the advanced tagging mechanism, including the security pitfalls to respect.

Interpolation and multiline

A template literal uses backticks and embeds expressions with ${ }. Newlines inside the backticks are preserved literally.

const name = 'Ada', count = 3
const msg = `Hello, ${name}! You have ${count} messages.`

const block = `Line one
Line two`        // real newline, no \n needed

Anything inside ${} is a full expression — arithmetic, function calls, ternaries, even nested template literals — evaluated and coerced to a string.

`Total: ${items.reduce((s, i) => s + i.price, 0)}`
`Status: ${active ? 'on' : 'off'}`   // ternary in a slot

Note: ${} holds an expression, not a statement — you can't put if/for there. For conditional content, use a ternary or compute the fragment into a variable first.

Why prefer them over concatenation

Template literals are more readable and less error-prone than + concatenation, which is noisy and prone to stray-space and coercion bugs.

'Hi ' + name + ', you have ' + count + ' items'   // noisy, easy to mis-space
`Hi ${name}, you have ${count} items`             // clear

They also make intent explicit: each slot is obviously a value being inserted, whereas + can silently produce NaN or '[object Object]' with the wrong operand.

Coercion inside slots

Each ${} result is converted to a string via the standard rules: objects call toString() (usually yielding the unhelpful [object Object]), arrays join with commas, and null/ undefined stringify literally.

`${[1, 2, 3]}`       // '1,2,3'
`${ {a: 1} }`        // '[object Object]' rarely useful
`${JSON.stringify({a: 1})}`   // '{"a":1}' usually what you want for objects

For objects you almost always want JSON.stringify rather than the default object coercion.

Tagged templates

A tagged template prefixes a template literal with a function. That function receives the array of string segments as its first argument and each interpolated value as subsequent arguments — giving you full control over the output.

function tag(strings, ...values) {
  return { strings, values }
}
tag`Hi ${name}, you are ${count}`
// strings: ['Hi ', ', you are ', '']  <- always one more than values
// values:  [name, count]

The strings array always has one more element than values, because the segments surround the interpolations. Tag functions zip them together — and crucially, the tag doesn't have to return a string at all.

String.raw and raw strings

The built-in String.raw tag returns the string with escape sequences uninterpreted — backslashes stay literal. This is invaluable for Windows paths, regex source, and LaTeX.

String.raw`C:\new\test`   // 'C:\\new\\test' — backslashes preserved
`C:\new\test`             // 'C:' + newline + 'ew' + tab + 'est'

Inside any tag, strings.raw holds the un-escaped segments, so custom tags can choose cooked or raw text. String.raw is literally implemented by joining strings.raw with the values.

Real-world tagged templates

Tagged templates power several popular libraries by parsing the literal at the call site:

// styled-components — CSS-in-JS
const Button = styled.button`
  color: ${props => props.primary ? 'white' : 'black'};
`

// graphql-tag — parses the query into an AST
const QUERY = gql`query { user { id name } }`

The tag receives the static chunks plus the dynamic interpolations and builds something domain-specific — a styled component, a parsed query, a translated message. This "embedded DSL" capability is the deepest use of template literals.

The security pitfall

Plain template literals perform no escaping — interpolated values go in verbatim. Building HTML or SQL by interpolation invites XSS and SQL injection.

el.innerHTML = `<div>${userInput}</div>`            // XSS if userInput has <script>
db.query(`SELECT * FROM users WHERE id = ${id}`)    // SQL injection

For HTML, use a sanitizing tag that escapes the values while trusting the static segments; for SQL, use parameterized queries. Never interpolate untrusted data directly into markup or queries.

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

Caching: the strings array is stable

A useful performance detail: for a given tagged-template call site, the engine reuses the same frozen strings array across invocations. Libraries exploit this to memoize parsed results (compiled CSS, GraphQL ASTs) keyed on the strings object identity.

function tag(strings) { /* same `strings` reference every call */ }
function render() { return tag`hello ${x}` }
// render() called repeatedly receives the identical strings array

Handling indentation and lists

Two practical gotchas. Multiline literals preserve source indentation, so nested code leaks leading spaces — strip with a dedent helper or regex. And rendering a list needs an explicit join, since default array coercion inserts commas.

`<ul>${items.map(i => `<li>${i}</li>`).join('')}</ul>`   // controlled separator
`<ul>${items}</ul>`   // inserts commas between items

Key takeaways

  • Template literals use backticks with ${expression} interpolation and preserve real newlines — cleaner than + concatenation.
  • Slots hold expressions (not statements); objects coerce to [object Object], so use JSON.stringify when needed.
  • Tagged templates call a function with the strings array (one longer than values) plus the interpolated values, enabling DSLs and full output control.
  • String.raw (and strings.raw) gives un-escaped text — ideal for paths and regex.
  • Plain interpolation does no escaping — use sanitizing tags for HTML and parameterized queries for SQL to prevent injection.
  • The per-call-site strings array is stable, enabling caching in libraries.

Template literals turn string building from a chore into a feature — and tagged templates extend that into embedded mini-languages, as long as you handle untrusted input safely.

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.