Skip to main content

🏹 Arrow Functions & Template Literals

Week 3: Modern JavaScript & Tooling β€” course module banner illustration

ES2015 (ES6) was the biggest upgrade JavaScript ever received β€” a decade's worth of ideas landing at once. Two of its features show up in nearly every line of modern code: arrow functions, a compact way to write functions that also fixes the notorious this problem, and template literals, which turn clumsy string concatenation into clean, readable interpolation.

Week 3 · Day 1 (Monday: ES6+ Features) · Lecture 1

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Convert traditional function expressions into arrow functions and choose the right level of brevity
  • Explain how arrow functions inherit this lexically, and when that helps or hurts
  • Decide, case by case, when an arrow function is the wrong tool
  • Use arrow functions fluently with map, filter, and reduce
  • Interpolate expressions, build multiline strings, and generate HTML with template literals
  • Write a tagged template to post-process a string before it is assembled

Estimated Time: 60 minutes

Practice: Refactor legacy callbacks to arrow syntax and build an HTML component generator.

In This Lesson

Why ES6 Changed Everything

Languages evolve the way natural languages do β€” new words and shorthands appear, and eventually everyone talks that way. ES6 (ECMAScript 2015) was JavaScript's dictionary-doubling moment. It landed let/const, classes, modules, promises, destructuring, and the two features we focus on today. Since then, the language ships a smaller update every year, but ES6 remains the dividing line between "old" and "modern" JavaScript.

graph LR A[JavaScript
1995] --> B[ES3
1999] B --> C[ES5
2009] C --> D[ES6 / ES2015
the big bang] D --> E[ES2016] E --> F[ES2017] F --> G[ES2020+
annual updates]

Everything in this lesson is universally supported in every current browser and in Node.js β€” no build step required. Let's start with the feature you'll type most often.

Arrow Function Syntax

An arrow function is a shorter way to write a function expression. Think of it as the compact car of functions: same job, less bodywork. The name comes from the => ("fat arrow") that sits between the parameters and the body.

Anatomy of an arrow function: parameters, fat arrow, and body const double = (n) => n * 2 parameters fat arrow implicit return body
A one-expression arrow returns its body automatically β€” no return, no braces.

Here is the same function written four ways, from most verbose to most terse:

// 1. Traditional function declaration
function add(a, b) {
    return a + b;
}

// 2. Arrow function with a block body β€” needs an explicit `return`
const add = (a, b) => {
    return a + b;
};

// 3. Arrow with an "implicit return" β€” one expression, no braces, no `return`
const add = (a, b) => a + b;

// 4. A single parameter can drop its parentheses
const double = n => n * 2;

// No parameters? Use empty parentheses:
const sayHello = () => "Hello!";

πŸ“– Implicit return, one gotcha

To return an object literal from an implicit-return arrow, wrap it in parentheses so JavaScript doesn't read the { } as a function body:

const makeUser = (name, age) => ({ name, age });   // βœ… returns an object
const broken   = (name, age) => { name, age };     // ❌ returns undefined

The this Superpower

Beyond looking nicer, arrow functions behave differently in one crucial way: they do not create their own this. Instead they capture this from the surrounding ("lexical") scope where they were written. A regular function is a chameleon β€” its this changes color depending on how it's called. An arrow function is a polar bear β€” it stays the same wherever you take it.

This solves the classic bug where this is lost inside a callback:

// ❌ The classic problem with a traditional callback
const button = {
    text: "Click me",
    onClick() {
        console.log(this.text);          // "Click me" β€” `this` is `button`
        setTimeout(function () {
            console.log(this.text);      // undefined! `this` is now the global object
        }, 1000);
    }
};

// βœ… The arrow function inherits `this` from onClick
const betterButton = {
    text: "Click me",
    onClick() {
        console.log(this.text);          // "Click me"
        setTimeout(() => {
            console.log(this.text);      // "Click me" β€” arrow keeps the outer `this`
        }, 1000);
    }
};

The same fix makes class event handlers painless. Because the arrow captures the instance's this, this.count refers to the object, not the DOM element that fired the event:

class Counter {
    constructor() {
        this.count = 0;
        this.button = document.createElement('button');
        this.button.textContent = 'Count: 0';

        // The arrow preserves `this`, so `this.count` is the instance's count
        this.button.addEventListener('click', () => {
            this.count++;
            this.button.textContent = `Count: ${this.count}`;
        });

        // A traditional callback here would break: `this` would be the button,
        // and `this.count` would be undefined.
    }
}

πŸ’‘ The mental model

Ask "where was this function written?" for an arrow, and "how is this function being called?" for a regular function. Arrows freeze this at authoring time; regular functions decide it at call time.

When (Not) to Use Arrows

Arrow functions are the default for short callbacks, but that same lexical this makes them the wrong choice in a few places. Use this decision tree:

graph TD A[Writing a function] --> B{Does it need its own 'this'
or 'arguments'?} B -->|Yes| C[Use a regular function] B -->|No| D{Is it an object method
or a class constructor?} D -->|Yes| C D -->|No| E[Use an arrow function]
Reach for an arrow when…Reach for a regular function when…
Writing a short callback (array methods, timers, promises)Defining an object method that uses this
You need to preserve the outer thisWriting a constructor (arrows can't be called with new)
Doing functional-style transformsYou need the arguments object
The body is a single expressionWriting a generator function (function*)

⚠️ The object-method trap

const timer = {
    seconds: 0,
    // ❌ arrow method: `this` is NOT `timer`, it's the outer scope
    tick: () => { this.seconds++; }
};
timer.tick();
console.log(timer.seconds);   // 0 β€” the arrow never touched `timer`

Use a normal method (tick() { this.seconds++; }) so this resolves to the object.

Arrows in Array Methods

This is where arrow functions truly shine. Passed to map, filter, and reduce, they read almost like plain English and keep the transformation front-and-center.

const numbers = [1, 2, 3, 4, 5];

// Verbose, pre-ES6
const doubled = numbers.map(function (num) {
    return num * 2;
});

// Concise, modern
const doubledArrow = numbers.map(num => num * 2);   // [2, 4, 6, 8, 10]

const users = [
    { name: 'Alice',   age: 25 },
    { name: 'Bob',     age: 30 },
    { name: 'Charlie', age: 35 }
];

// Chain transforms: keep users over 28, then take their names
const olderUsers = users
    .filter(user => user.age > 28)
    .map(user => user.name);
console.log(olderUsers);   // ['Bob', 'Charlie']

// reduce boils a list down to a single value
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
console.log(totalAge);     // 90

Output

doubledArrow β†’ [2, 4, 6, 8, 10]
olderUsers   β†’ ['Bob', 'Charlie']
totalAge     β†’ 90

Template Literals

Before ES6, building a string from variables meant gluing pieces together with + β€” error-prone and hard to read. Template literals use backticks (`) and ${ } placeholders so you can drop values (and any expression) straight into the text, Mad-Libs style.

const name = "Alice";
const age  = 25;

// Old way β€” concatenation
const message = "Hello, my name is " + name + " and I am " + age + " years old.";

// New way β€” interpolation
const better = `Hello, my name is ${name} and I am ${age} years old.`;

// Any expression works inside ${ }
const a = 10, b = 20;
console.log(`The sum of ${a} and ${b} is ${a + b}`);   // ...is 30

// Function calls and ternaries too
const score = 85;
console.log(`You ${score >= 60 ? 'passed' : 'failed'} the exam!`);

Template literals also preserve line breaks, which makes multiline strings (and generated HTML) far cleaner:

// Multiline the old way
const oldMultiline = "Line 1\n" +
                     "Line 2\n" +
                     "Line 3";

// Multiline with a template literal β€” the newlines are literal
const newMultiline = `Line 1
Line 2
Line 3`;

Generating HTML

A very common real-world use is building markup from data. Nesting .map(...).join('') inside a template literal lets you render a list without any framework:

const createCard = user => `
    <div class="user-card">
        <h3>${user.name}</h3>
        <p>${user.bio}</p>
        <button>${user.isFollowing ? 'Unfollow' : 'Follow'}</button>
    </div>`;

const createTable = (headers, rows) => `
    <table>
        <thead>
            <tr>${headers.map(h => `<th>${h}</th>`).join('')}</tr>
        </thead>
        <tbody>
            ${rows.map(row => `<tr>${row.map(cell => `<td>${cell}</td>`).join('')}</tr>`).join('')}
        </tbody>
    </table>`;

⚠️ Never inject untrusted input as HTML

Dropping user-supplied text into an HTML template can create a cross-site-scripting (XSS) hole. When the value comes from a user, set it with element.textContent or escape it first β€” don't concatenate it into markup.

Tagged Templates

A tagged template lets a function intercept a template literal before the final string is built. You put a function name directly in front of the backticks; it receives the static string pieces as its first argument and each interpolated value as the rest.

function highlight(strings, ...values) {
    // `strings` is the static text; `values` are the ${...} results
    return strings.reduce((result, str, i) => {
        const value = values[i] !== undefined ? `<mark>${values[i]}</mark>` : '';
        return `${result}${str}${value}`;
    }, '');
}

const lang = "JavaScript";
const year = 2015;
const out = highlight`${lang} was significantly updated in ${year}.`;
console.log(out);
// "<mark>JavaScript</mark> was significantly updated in <mark>2015</mark>."

Tagged templates power real tools: the css and gql tags in libraries like styled-components and GraphQL, and safe-SQL builders that separate the query text from its parameters:

// A tiny parameterized-query builder β€” values never touch the SQL text
function sql(strings, ...values) {
    return { text: strings.join('?'), values };
}

const userId = 123, status = 'active';
const query = sql`SELECT * FROM users WHERE id = ${userId} AND status = ${status}`;
console.log(query);
// { text: "SELECT * FROM users WHERE id = ? AND status = ?", values: [123, "active"] }

Practice & Quiz

πŸ‹οΈ Exercise 1: Refactor to arrows + template literals

Goal: Take the ES5 code below and rewrite it with arrow functions and a template literal. It should keep only electronics over $500 and format each as "Name: $price".

const products = [
    { name: 'Laptop', price: 999, category: 'Electronics' },
    { name: 'Book',   price: 20,  category: 'Education'    },
    { name: 'Phone',  price: 699, category: 'Electronics' }
];

// Rewrite this:
const expensive = products
    .filter(function (p) {
        return p.category === 'Electronics' && p.price > 500;
    })
    .map(function (p) {
        return p.name + ': $' + p.price;
    });
// Expected: ["Laptop: $999", "Phone: $699"]
πŸ’‘ Hint

Each callback body is a single expression, so you can drop the braces and the return. Use a template literal `${p.name}: $${p.price}` in the .map().

βœ… Solution
const expensive = products
    .filter(p => p.category === 'Electronics' && p.price > 500)
    .map(p => `${p.name}: $${p.price}`);

console.log(expensive);   // ["Laptop: $999", "Phone: $699"]

πŸ‹οΈ Exercise 2: A profile-card generator

Goal: Write generateProfile(user) that returns an HTML string with the user's name, bio, and a bulleted skills list built from the skills array.

const user = {
    name: 'John Doe',
    bio: 'Web developer',
    skills: ['JavaScript', 'React', 'Node.js']
};
// generateProfile(user) should include an <li> per skill
πŸ’‘ Hint

Map the skills array to <li> strings and .join('') them inside a nested ${ }.

βœ… Solution
const generateProfile = user => `
    <article class="profile">
        <h3>${user.name}</h3>
        <p>${user.bio}</p>
        <ul>
            ${user.skills.map(skill => `<li>${skill}</li>`).join('')}
        </ul>
    </article>`;

console.log(generateProfile(user));

🎯 Quick Quiz

Question 1: What does const f = () => ({ ok: true }); return when called?

Question 2: Inside setTimeout(() => { ... }, 1000) written in a method, what is this?

Question 3: Which task is a tagged template best suited for?

Best Practices & Pitfalls

βœ… Do

  • Use arrow functions for short callbacks and array-method transforms
  • Wrap a returned object literal in parentheses: () => ({ ... })
  • Reach for template literals whenever you're interpolating or writing multiline text
  • Break a complex arrow into a block body with named intermediate variables when it stops being readable

❌ Don't

  • Use an arrow for an object method or a class constructor that needs its own this
  • Rely on arguments inside an arrow β€” use a rest parameter (...args) instead
  • Use a template literal for a plain constant string (`Hello`) β€” a normal quote is fine
  • Inject untrusted user input into an HTML template literal

βœ… Keep chains readable

// Hard to scan on one line:
const r = data => data.filter(x => x > 0).map(x => x * 2).reduce((a, b) => a + b);

// Clearer with a block body and named steps:
const r = data => {
    const positives = data.filter(x => x > 0);
    const doubled   = positives.map(x => x * 2);
    return doubled.reduce((a, b) => a + b, 0);
};

Summary

πŸŽ‰ Key Takeaways

  • Arrow functions are concise function expressions; a single-expression body returns implicitly
  • They inherit this lexically, which fixes the classic lost-this-in-a-callback bug
  • That same behavior makes them wrong for object methods and constructors
  • Arrows read beautifully with map, filter, and reduce
  • Template literals give you interpolation, multiline strings, and HTML generation with backticks and ${ }
  • Tagged templates let a function transform the parts and values before assembly

πŸ“š Additional Resources

πŸš€ What's Next?

Now that you can write tidy functions and strings, the next lesson unpacks two more ES6 workhorses that appear everywhere in React and Node code: Destructuring and the Spread Operator β€” pulling values out of objects and arrays, and spreading them back in.

πŸŽ‰ Great work!

You just learned two features you'll use in nearly every file from here on out.