Skip to main content

Command Palette

Search for a command to run...

JavaScript Template Literals

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

Updated
JavaScript Template Literals
M

👋 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 \n

  • A 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:

  1. String interpolation — embed variables and expressions directly

  2. Multi-line strings — no \n, no hacks

  3. Tagged 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, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
  );

  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, &lt;script&gt;alert('xss')&lt;/script&gt;</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 surgery

  • Security 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:

  1. String methods (padStart, trimEnd, replaceAll, matchAll) — the built-in utilities that pair naturally with template literals for formatting and transformation

  2. Regular Expressions in JS — once your strings are readable, regex lets you search, validate, and transform them with precision

  3. Intl API — JavaScript's internationalization API for formatting dates, numbers, and currencies correctly across locales (pairs perfectly with tagged templates)

  4. 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 Intl API: Format dates, currencies, and numbers for any locale without a library — severely underused and incredibly powerful

  • JavaScript Proxy and Reflect: How to intercept and customize object behavior at runtime — the foundation of many reactive frameworks

  • Async/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. 🚀

Zero to Full Stack Developer: From Basics to Production

Part 27 of 50

Complete full-stack web development series from zero to production. Learn HTML, CSS, JavaScript, TypeScript, React, Next.js, Node.js, databases, Docker, AWS, and AI integration. Build real-world projects step-by-step.

Up next

JavaScript new Keyword: Under the Hood

A practical guide to constructor functions, prototype chains, and how JavaScript builds objects from scratch.