Lesson 9 / 11

Closures and Higher-Order Functions

A closure is a function that remembers the variables from the scope it was created in, even after that outer scope has already finished running. This sounds abstract until you see it solve a real problem.

function makeCounter() {
  let count = 0;
  return function () {
    count++;
    return count;
  };
}

const counter = makeCounter();
console.log(counter());   // 1
console.log(counter());   // 2

Each call to makeCounter() creates a brand new, private count variable that only the returned inner function can see or modify — count isn’t accessible from outside at all. This is how you get private state in JavaScript without needing a class, and it’s the exact mechanism behind the progress-tracking JavaScript on this very site.

A second counter is independent

const counterA = makeCounter();
const counterB = makeCounter();
console.log(counterA());   // 1
console.log(counterA());   // 2
console.log(counterB());   // 1 -- completely separate from counterA

Higher-order functions

function applyDiscount(percent) {
  return function (price) {
    return price * (1 - percent / 100);
  };
}

const tenPercentOff = applyDiscount(10);
console.log(tenPercentOff(200));   // 180

A function that takes another function as an argument, or returns one, is called a higher-order function. You’ve already been using higher-order functions constantly without necessarily naming them that way — map, filter, reduce, and addEventListener are all higher-order functions.

Why this matters

Closures explain a lot of behavior that otherwise looks like magic — why a variable inside a setTimeout callback still has the value it had when the timeout was created, or why event handlers can each remember which specific button they belong to. Once closures click, a large amount of JavaScript stops feeling mysterious.