Template literals in JavaScript
Template literals are strings wrapped in backticks (`) that let you embed expressions with ${} syntax, write multi-line strings without escape sequences, and use tagged templates for advanced processing. Introduced in ES6.
Theory
TL;DR
- Template literals work like mail merge: one template, inject data where needed
- Main difference: backticks +
${}vs concatenation with+ - Use them whenever a string has variables, multi-line content, or dynamic values
- Static strings with no variables? Regular quotes are clearer there
- Inside
${}you can use any expression: math, function calls, ternary operators
Quick example
const name = "Alice";
const age = 28;
// Old way - concatenation
const old = "Hello, " + name + "! You are " + age + " years old.";
// Template literal
const modern = `Hello, ${name}! You are ${age} years old.`;
// "Hello, Alice! You are 28 years old."
// Multi-line - no \n needed
const message = `Welcome, ${name}!
Your account is active.
Age: ${age}`;The backtick syntax removes the chain of + operators between every variable and string piece.
Key difference
Template literals evaluate expressions inside ${} at runtime and convert results to strings automatically. With concatenation, you chain pieces with + and need \n for line breaks. Template literals handle both. They also unlock tagged templates, where a function receives the string parts and expression values separately before anything is concatenated. Regular strings cannot do that.
When to use
- Any string that mixes text with variables
- Multi-line content: HTML snippets, SQL queries, error messages
- Complex expressions:
${user.isAdmin ? 'admin' : 'user'} - Dynamic attributes and class names in components
- Avoid for plain static strings with no dynamic parts - regular quotes read more clearly there
In practice, once you switch to template literals for dynamic strings, concatenation feels clunky even for simple two-part cases.
How it works internally
When the JS engine hits a template literal, it parses the backtick-delimited string and locates each ${} block. Each expression gets evaluated in the current scope, converted via .toString(), and inserted at that position. For tagged templates, the engine splits the static string parts and evaluated expression values into separate arguments, then passes them to the tag function before any string building happens. Structure is resolved at parse time; expression evaluation happens at runtime.
Common mistakes
Forgetting the $ in ${}
const name = "Alice";
// ❌ Prints literal curly braces
const greeting = `Hello, {name}!`;
console.log(greeting); // "Hello, {name}!" - not what you wanted
// ✅ Correct
const greeting2 = `Hello, ${name}!`;
console.log(greeting2); // "Hello, Alice!"Template literals as object keys
const key = "user";
// ❌ The key becomes the literal string "${key}_name"
const bad = { [`${key}_name`]: "Alice" }; // needs square brackets
// Without brackets: { '${key}_name': 'Alice' } - wrong
// ✅ Computed property syntax with square brackets
const obj = { [`${key}_name`]: "Alice" };
console.log(obj); // { user_name: 'Alice' }Unescaped user input (XSS)
const userInput = "<img src=x onerror='alert(1)'>";
// ❌ The script executes when rendered
const html = `<div>${userInput}</div>`;
// ✅ Escape before injecting
function escapeHtml(text) {
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
return text.replace(/[&<>"']/g, m => map[m]);
}
const safeHtml = `<div>${escapeHtml(userInput)}</div>`;
// <div><img src=x onerror='alert(1)'></div> - safeUnexpected whitespace in multi-line templates
// ❌ Includes a leading newline and indentation spaces
const html = `
<div>
<p>Content</p>
</div>
`;
// ✅ Use .trim() or skip the leading newline
const html2 = `<div>
<p>Content</p>
</div>`.trim();Real-world usage
- React: dynamic
classNamestrings andaria-labelattributes - Express/Node.js: SQL query strings, log messages, error responses
- Apollo Client: GraphQL queries via the
gqltagged template - styled-components: CSS-in-JS with tagged template syntax
- Jest: test descriptions and snapshot strings
Follow-up questions
Q: What happens if you put an object inside ${}?
A: It calls .toString() on the value, which gives [object Object]. Use JSON.stringify(obj) explicitly if you need the actual content.
Q: Can you use template literals as object property names?
A: Not directly. Without square brackets the backtick string is treated as a literal key. You need computed property syntax: { [\key_${x}`]: value }`.
Q: How do tagged templates actually work?
A: The tag is a function that receives the static string parts as an array and the evaluated expression values as separate arguments: (strings, ...values). The function decides how to combine them. That is how SQL tag functions escape query parameters and how styled-components processes CSS strings.
Q: Are template literals slower than concatenation?
A: No. Modern engines like V8 and SpiderMonkey optimize both. The performance difference is negligible. The reason to prefer template literals is readability.
Q: (Senior) How do you build a tagged template that automatically prevents XSS?
A: Write a tag function that maps over the values array and escapes each one before insertion. It receives (strings, ...values) - escape each value and interleave with the strings parts to build the final string. This is exactly how lit-html and htm handle user-provided content.
Examples
Basic interpolation and multi-line string
const user = { name: "Alice", email: "alice@example.com" };
const isActive = true;
const card = `
Name: ${user.name}
Email: ${user.email}
Status: ${isActive ? "active" : "inactive"}
Joined: ${new Date().toLocaleDateString()}
`.trim();
console.log(card);
// Name: Alice
// Email: alice@example.com
// Status: active
// Joined: 1/15/2025Ternary operators, method calls, and property access all work inside ${}. The .trim() removes the leading and trailing newlines that come from the backtick wrapping.
Tagged template for SQL query safety
function sql(strings, ...values) {
const escaped = values.map(v =>
typeof v === "string" ? `'${v.replace(/'/g, "''")}'` : v
);
let result = strings[0];
for (let i = 0; i < escaped.length; i++) {
result += escaped[i] + strings[i + 1];
}
return result;
}
const userId = 42;
const userName = "O'Brien"; // contains an apostrophe
const query = sql`SELECT * FROM users WHERE id = ${userId} AND name = ${userName}`;
console.log(query);
// SELECT * FROM users WHERE id = 42 AND name = 'O''Brien'
// The apostrophe is escaped - SQL injection blockedThe tag function runs before the string is built. It receives the static parts in strings and the evaluated expressions in values. This is the same pattern Apollo Client uses for GraphQL queries with the gql tag.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.