JavaScript Template Literals
Write cleaner, safer strings — and understand the feature powering styled-components, Apollo, and postgres.js.

👋 Hey, I'm Mohd Kaif – a student documenting my journey through code. I write about what I'm learning in real-time – the wins, the struggles, and the "aha!" moments. From JavaScript and React to backend systems with Node.js, databases, DevOps, TypeScript, and AI integrations. This blog is my public learning journal: honest, evolving, and always exploring. If you're curious about any of these topics, let's learn and build together!
Ever noticed how a single line of JavaScript string concatenation can turn into a puzzle no one wants to solve?
You're staring at a line like this:
const msg = "Hello " + user.name + ", you have " + count + " new messages as of " + date + ".";
And you think — there has to be a better way. Maybe you've felt one of these:
You spent 10 minutes debugging a string only to find a missing space after a
+You tried writing multi-line HTML in a string and ended up with a wall of
\nA teammate handed you a 200-character concatenated string and you quietly closed the file
You've seen
${}in someone else's code and weren't sure what it does or when to use it
The problem isn't your JavaScript skills. String concatenation is genuinely painful at scale — it was a known limitation of the language long before a better solution arrived.
If any of this sounds familiar, you're in exactly the right place.
✅ What You'll Learn
How template literals differ from traditional string concatenation (and why it matters)
How to embed variables, expressions, and logic directly inside strings
How to write clean multi-line strings without hacks
Why tagged template literals are the most underused power feature in modern JS
When template literals are used in real-world production code (with concrete examples)
What mistakes to avoid — including one that could introduce security vulnerabilities
No prerequisites required — if you know what a variable is in JavaScript, you're ready.
The Old Way: String Concatenation and Its Real Cost
Before ES6, building dynamic strings meant using + to glue pieces together:
const name = "Rahul";
const age = 25;
const city = "Mumbai";
const message = "Hi, my name is " + name + ". I am " + age + " years old and I live in " + city + ".";
This works. But watch what happens when the string grows:
// Building an HTML card the old way
const card = "<div class='card'>" +
"<h2>" + user.name + "</h2>" +
"<p>Age: " + user.age + "</p>" +
"<p>Email: " + user.email + "</p>" +
"<span class='" + (user.active ? "active" : "inactive") + "'>" + user.status + "</span>" +
"</div>";
What goes wrong here?
| Problem | Why It Hurts |
|---|---|
| Manual spacing | A missing space before/after + silently breaks output |
| Nested quotes | Mixing ' and " to avoid conflicts adds mental overhead |
| Multi-line strings | Requires \n or backslash continuation — both are fragile |
| Conditional logic | Ternaries inside + chains are nearly unreadable |
| Maintenance | Changing the structure requires careful surgery on the + chain |
Messy strings aren't just an aesthetic problem — they slow down debugging, make code reviews harder, and are a common source of subtle bugs.
Template Literals: The Fix That Shipped in ES6
ES6 (ECMAScript 2015) introduced template literals — a new string syntax that replaces quotes with backticks (`).
const message = `This is a template literal`;
At first glance, it looks like a small cosmetic change. It's not. Backticks unlock three fundamental capabilities:
String interpolation — embed variables and expressions directly
Multi-line strings — no
\n, no hacksTagged templates — process strings through a function (more on this shortly)
Feature 1: String Interpolation
The ${} syntax lets you embed any JavaScript expression directly inside a string.
const name = "Rahul";
const age = 25;
const message = `Hi, my name is \({name} and I am \){age} years old.`;
console.log(message);
// → Hi, my name is Rahul and I am 25 years old.
How interpolation works (visualized)
Template string:
`Hi, my name is \({name} and I am \){age} years old.`
↑ ↑
evaluates to evaluates to
"Rahul" 25
↓ ↓
Result:
"Hi, my name is Rahul and I am 25 years old."
The ${} isn't just variable substitution — JavaScript evaluates whatever expression is inside it:
const a = 10;
const b = 20;
console.log(`The sum of \({a} and \){b} is ${a + b}.`);
// → The sum of 10 and 20 is 30.
const isLoggedIn = true;
console.log(`User is currently ${isLoggedIn ? "online" : "offline"}.`);
// → User is currently online.
const items = ["apple", "mango", "banana"];
console.log(`You have \({items.length} item\){items.length !== 1 ? "s" : ""} in your cart.`);
// → You have 3 items in your cart.
Before vs. After: Side by Side
// ❌ Before — string concatenation
const greeting = "Good " + timeOfDay + ", " + user.name + "! You have " + inbox.length + " unread messages.";
// ✅ After — template literal
const greeting = `Good \({timeOfDay}, \){user.name}! You have ${inbox.length} unread messages.`;
The second version reads like plain English. That's the point.
🏋️ Exercise 1 — Try It Yourself
Open your browser console and run this:
const product = "Laptop";
const price = 79999;
const discount = 10;
// Using template literals, log this exact output:
// "The Laptop is priced at ₹79999. After a 10% discount, you pay ₹71999.10."
Hint: You'll need ${price - (price * discount / 100)} inside your template. Try it before scrolling on.
Feature 2: Multi-line Strings Without the Mess
Here's what writing multi-line strings looked like before ES6:
// ❌ The old way — fragile and unpleasant
const html = "<div class='card'>\n" +
" <h2>Hello</h2>\n" +
" <p>Welcome back.</p>\n" +
"</div>";
With template literals, the newline is just... a newline:
// ✅ The new way — write it exactly how it looks
const html = `
<div class='card'>
<h2>Hello</h2>
<p>Welcome back.</p>
</div>
`;
Before vs. After: Multi-line
❌ Old Way ✅ Template Literal
─────────────── ───────────────
"SELECT * FROM users\n" + `SELECT *
"WHERE active = true\n" + FROM users
"ORDER BY created_at DESC"; WHERE active = true
ORDER BY created_at DESC`
This matters enormously for:
HTML templates rendered from JavaScript
SQL queries built in Node.js
Email content with dynamic sections
Markdown or report generation
Feature 3: Tagged Template Literals — The Underrated Powerhouse
This is where most articles stop. It's also where template literals go from convenient to genuinely powerful.
A tagged template lets you pass a template literal through a function before it resolves into a string. The function receives the static parts and the interpolated values separately — giving you full control over the output.
The Syntax
function myTag(strings, ...values) {
// strings → array of static text segments
// values → array of interpolated expression results
}
const result = myTag`Hello \({name}, you have \){count} messages.`;
Practical Example 1 — Highlight Dynamic Values
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] !== undefined ? `<mark>${values[i]}</mark>` : "";
return result + str + value;
}, "");
}
const user = "Rahul";
const score = 98;
const output = highlight`User \({user} scored \){score} points.`;
console.log(output);
// → User <mark>Rahul</mark> scored <mark>98</mark> points.
Practical Example 2 — Sanitizing User Input (Security Use Case)
This is one you'll actually use in production. When you inject user-supplied values into HTML via template literals, you risk XSS (Cross-Site Scripting) attacks:
// ❌ Dangerous — if userInput is "<script>steal()</script>", you have a problem
const html = `<p>Welcome, ${userInput}!</p>`;
A tagged template can sanitize on the way in:
function safeHTML(strings, ...values) {
const escaped = values.map(val =>
String(val)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
);
return strings.reduce((result, str, i) => result + str + (escaped[i] ?? ""), "");
}
const userInput = `<script>alert('xss')</script>`;
const html = safeHTML`<p>Welcome, ${userInput}!</p>`;
console.log(html);
// → <p>Welcome, <script>alert('xss')</script></p>
The script tag is neutralized before it ever touches the DOM. This is exactly how libraries like styled-components, Apollo's gql, and postgres.js work under the hood — they're all tagged templates.
Real Libraries Built on Tagged Templates
| Library | Tag | What It Does |
|---|---|---|
styled-components |
styled.div`...` |
Parses CSS with dynamic props |
| Apollo GraphQL | gql`...` |
Parses GraphQL query strings |
postgres.js |
sql`...` |
Builds safe parameterized queries |
lit-html |
html`...` |
Renders efficient DOM templates |
These aren't exotic edge cases — tagged templates are one of the most widely used advanced JS features in the entire ecosystem.
🏋️ Exercise 2 — Build a Currency Formatter Tag
Try writing a tagged template that formats any number as a currency string:
function currency(strings, ...values) {
// Your code here
// Hint: format values with toLocaleString('en-IN', { style: 'currency', currency: 'INR' })
}
const price = 1500;
const tax = 270;
console.log(currency`Subtotal: \({price}, Tax: \){tax}, Total: ${price + tax}`);
// Expected: "Subtotal: ₹1,500.00, Tax: ₹270.00, Total: ₹1,770.00"
Real-World Use Cases in Modern JavaScript
Here's where template literals appear most often in production codebases:
1. Dynamic UI Rendering
function renderUserCard(user) {
return `
<div class="card ${user.active ? "card--active" : "card--inactive"}">
<h3>${user.name}</h3>
<p>📧 ${user.email}</p>
<p>📍 ${user.city}</p>
<span class="badge">${user.role.toUpperCase()}</span>
</div>
`;
}
2. Building API Endpoints Dynamically
const BASE_URL = "https://api.example.com/v2";
const endpoints = {
user: (id) => `\({BASE_URL}/users/\){id}`,
posts: (userId, limit = 10) => `\({BASE_URL}/users/\){userId}/posts?limit=${limit}`,
search: (query) => `\({BASE_URL}/search?q=\){encodeURIComponent(query)}`,
};
fetch(endpoints.posts(42, 5)); // https://api.example.com/v2/users/42/posts?limit=5
3. Structured Logging
function log(level, event, meta = {}) {
console.log(`[\({new Date().toISOString()}] [\){level.toUpperCase()}] \({event} \){JSON.stringify(meta)}`);
}
log("info", "User login", { userId: 123, ip: "192.168.1.1" });
// → [2025-04-22T10:30:00.000Z] [INFO] User login {"userId":123,"ip":"192.168.1.1"}
4. Generating Email or Notification Content
function buildEmailBody({ name, plan, renewalDate, amount }) {
return `
Hi ${name},
Your \({plan} plan renews on \){renewalDate}.
The amount of ₹${amount} will be charged to your card on file.
If you have any questions, reply to this email.
— The Team
`.trim();
}
Common Mistakes to Avoid
❌ Mistake 1: Using Quotes Instead of Backticks
// Wrong — ${name} is treated as literal text
const msg = "Hello ${name}";
// Correct
const msg = `Hello ${name}`;
❌ Mistake 2: Burying Complex Logic Inside ${}
// Hard to read and debug
const msg = `Total: ${items.filter(i => i.active).map(i => i.price).reduce((a, b) => a + b, 0)}`;
// Extract first, then interpolate
const total = items
.filter(i => i.active)
.map(i => i.price)
.reduce((a, b) => a + b, 0);
const msg = `Total: ${total}`;
❌ Mistake 3: Unintended Whitespace in Multi-line Templates
// This looks clean...
function getGreeting(name) {
return `
Hello, ${name}!
`;
}
// ...but includes a leading newline and trailing spaces
console.log(getGreeting("Rahul").length); // Not what you'd expect!
// Fix with .trim()
return `
Hello, ${name}!
`.trim();
❌ Mistake 4: Injecting Unsanitized User Input into HTML
As shown in the tagged templates section — if you're building HTML strings with user-supplied data, always sanitize. A plain template literal gives you no protection. Use a safeHTML tag or a library like DOMPurify.
🏋️ Exercise 3 — Spot the Bug
Find and fix all the problems in this code snippet:
const user = { name: "Priya", score: 87, rank: 3 };
const summary = "Player " + `${user.name}` + " finished in rank " + user.rank +
" with a score of ${user.score} points. " +
`Great job ${user.name}`;
console.log(summary);
There are 3 issues. Find them before looking at the answer below.
✅ See the answer
// Issues:
// 1. Unnecessary mixing of "+" concatenation with template literals
// 2. "${user.score}" is inside double quotes — won't interpolate
// 3. Missing punctuation at the end of the last template literal
// Fixed version:
const summary = `Player \({user.name} finished in rank \){user.rank} with a score of \({user.score} points. Great job \){user.name}!`;
Why This Matters Beyond Syntax
Template literals aren't just about writing less +. In production systems, code clarity has compounding effects:
Faster onboarding — new developers understand dynamic strings at a glance
Fewer bugs in review — readable strings are easier to spot-check for logic errors
Easier refactoring — restructuring a template literal is mechanical; untangling a
+chain is surgerySecurity by design — tagged templates give you a structured place to enforce sanitization
The shift from concatenation to template literals is small syntactically, but it reflects a broader principle: write code for the human reading it next, not just for the machine running it now.
What to Learn Next
Template literals are one piece of the modern JavaScript string toolkit. Here's where to go from here:
Stringmethods (padStart,trimEnd,replaceAll,matchAll) — the built-in utilities that pair naturally with template literals for formatting and transformationRegular Expressions in JS — once your strings are readable, regex lets you search, validate, and transform them with precision
IntlAPI — JavaScript's internationalization API for formatting dates, numbers, and currencies correctly across locales (pairs perfectly with tagged templates)Functional patterns for string building — how large codebases structure dynamic content generation using reducers and builder functions
💬 Got Questions?
Drop a comment below! I'd love to hear how you're using template literals in your own projects, or help you work through any part of this that wasn't clear.
Here are the topics coming up in future articles:
Destructuring in JavaScript: How to pull values out of objects and arrays cleanly — one of the most used ES6 features in real codebases
The
IntlAPI: Format dates, currencies, and numbers for any locale without a library — severely underused and incredibly powerfulJavaScript
ProxyandReflect: How to intercept and customize object behavior at runtime — the foundation of many reactive frameworksAsync/Await Deep Dive: Beyond the basics — error handling patterns, parallel execution, and avoiding the common pitfalls that slow down async code
Found this helpful? Share it with someone learning JavaScript — it might save them an hour of debugging a broken + chain. 🚀




