
Template literals, introduced in ES6, replace messy string concatenation with a cleaner, more readable syntax. This post covers the problems they solve, how they work, and where they shine in modern JavaScript.
Imagine writing a letter by hand where every time you need to include someone's name, you have to stop writing, tape in a sticky note, and then resume writing. That's essentially what old-school JavaScript string concatenation felt like. Template literals are the upgrade: you just write the name right there, inline, without breaking your flow.
Before template literals, building dynamic strings in JavaScript meant gluing pieces together with the + operator. For simple cases it was fine. For anything real, it became a maintenance nightmare.
javascript// User profile message const name = "Atharv"; const role = "developer"; const years = 3; const message = "Hello, " + name + "! You are a " + role + " with " + years + " years of experience.";
Count the quotes. Count the + signs. Count the spaces you have to manually insert before and after variables. Miss one and your output reads "Hello,Atharv!" with no space. Now imagine a URL with query params, or an HTML string built dynamically. It gets ugly fast.
Old Way: "Hello, " + name + "! You are a " + role + " with " + years + " years." ↑ ↑ ↑ ↑ ↑ ↑ open close open close open close quote + quote + quote + quote + quote + variable Every variable needs quotes around the surrounding text. Every space must be manually placed. Every typo is silent.
Multi-line strings were even worse:
javascript// Old multi-line: use \n and pray const html = "<div>\n" + " <h1>" + title + "</h1>\n" + " <p>" + body + "</p>\n" + "</div>";
Template literals use backticks instead of single or double quotes. That single change unlocks everything.
javascriptconst greeting = `Hello, world!`;
Looks similar so far. Here's where it gets useful.
Inside a template literal, ${} is your escape hatch into JavaScript. Anything inside those curly braces gets evaluated and embedded directly into the string.
javascriptconst name = "Atharv"; const role = "developer"; const years = 3; const message = `Hello, ${name}! You are a ${role} with ${years} years of experience.`;
No quotes to open and close around every piece. No + operators. The string reads like a sentence.
Template Literal Interpolation: `Hello, ${name}! You are a ${role} with ${years} years.` ┌──┴──┐ ┌──┴──┐ ┌───┴───┐ │name │ │role │ │ years │ │"Atharv" │"developer" │ 3 │ └─────┘ └─────┘ └───────┘ ↓ "Hello, Atharv! You are a developer with 3 years."
The ${} can hold any valid JavaScript expression, not just variable names:
javascriptconst a = 10; const b = 5; console.log(`Sum: ${a + b}`); // Sum: 15 console.log(`Max: ${Math.max(a, b)}`); // Max: 10 console.log(`Even? ${a % 2 === 0}`); // Even? true
You can call functions, run ternary operators, access object properties: anything that evaluates to a value.
javascriptconst user = { name: "Atharv", isPro: true }; const label = `${user.name} (${user.isPro ? "Pro" : "Free"})`; // "Atharv (Pro)"
This is where template literals make old code look embarrassing. Newlines are preserved exactly as written. No \n, no string concatenation across lines.
Old Way vs Template Literal: ┌─────────────────────────────────────────────┐ │ Old Way │ │ │ │ "<div>\n" + │ │ " <p>" + text + "</p>\n" + │ │ "</div>" │ └─────────────────────────────────────────────┘ ┌─────────────────────────────────────────────┐ │ Template Literal │ │ │ │ `<div> │ │ <p>${text}</p> │ │ </div>` │ │ │ │ What you write is what you get. │ └─────────────────────────────────────────────┘
javascriptconst title = "Modules"; const body = "Split your code into focused files."; const card = ` <div class="card"> <h2>${title}</h2> <p>${body}</p> </div> `;
The indentation, the newlines, all of it is preserved in the output. This is massive for building HTML strings, SQL queries, or any multi-line text.
URL building: Query strings used to be miserable to write.
javascriptconst base = "https://api.example.com"; const userId = 42; const page = 2; const url = `${base}/users/${userId}/posts?page=${page}`; // "https://api.example.com/users/42/posts?page=2"
Logging with context: Print meaningful debug messages without the concatenation noise.
javascriptconsole.log( `[ERROR] fetchUser failed for userId: ${userId} at ${new Date().toISOString()}`, );
Tagged Template Literals (Advanced): This is a feature most developers don't know exists. You can prefix a template literal with a function name, which intercepts the string before it's assembled. Libraries use this for SQL query sanitization, styled components, and internationalization.
javascriptfunction highlight(strings, ...values) { return strings.reduce((result, str, i) => { return result + str + (values[i] ? `<mark>${values[i]}</mark>` : ""); }, ""); } const term = "modules"; const output = highlight`Learn about ${term} in JavaScript`; // "Learn about <mark>modules</mark> in JavaScript"
Tagged Template Flow: highlight`Learn about ${term} in JavaScript` ↓ strings: ["Learn about ", " in JavaScript"] values: ["modules"] ↓ Custom function processes both arrays ↓ Returns: "Learn about <mark>modules</mark> in JavaScript"
The styled-components library is built entirely on this mechanism: styled.div`color: red;` is a tagged template literal call.
+ doesn't scale. Template literals replace it cleanly.${} interpolation: any JavaScript expression can live inside.\n, no line continuation hacks.${} block is not limited to variables: ternaries, function calls, math, all valid.Template literals are one of those ES6 features you adopt once and immediately wonder how you lived without. The readability improvement alone justifies the switch. And once you understand tagged templates, you start recognizing them everywhere in the ecosystem.
Related posts based on tags, category, and projects
Destructuring is a concise syntax for pulling values out of arrays and objects into individual variables. This post covers array destructuring, object destructuring, default values, and the real-world patterns where this shines.
Spread and rest both use `...` but do opposite things. Spread expands an iterable into individual elements. Rest collects individual elements into a single array. This post breaks down each one clearly with real examples.
Promises are one of those JavaScript concepts that sound intimidating but click instantly once you see the right mental model. This post breaks them down from scratch, covering why they exist, how they work, and how to use them properly.
Object-Oriented Programming (OOP) is a way of organizing code around real-world entities, and JavaScript supports it natively through classes. This post walks you through what OOP actually means, how classes work in JS, and why it makes your code cleaner and more reusable.