Skip to content
Laviyx Learn

Try

JavaScript course menu

JavaScript Closures Explained with Examples

Understand JavaScript closures: functions that remember variables from where they were created, counters and private state, function factories, closures in loops (var vs let), memoization, and practical uses in event handlers and modules.

Beginner9 min readUpdated
On this page

A closure is a function together with the variables it can see from the place where it was created. The function "remembers" those variables, even after the outer function has finished running. Closures sound abstract, but you use them every time you write a callback.

A function that remembers#

JavaScript
function makeGreeter(greeting) {
  return function (name) {
    return `${greeting}, ${name}!`;
  };
}

const sayHello = makeGreeter("Hello");
const sayNamaste = makeGreeter("Namaste");

console.log(sayHello("Asha"));
console.log(sayNamaste("Ravi"));
Output
Hello, Asha!
Namaste, Ravi!

makeGreeter has finished by the time sayHello("Asha") runs, yet the inner function still knows greeting. Each call to makeGreeter creates a separate closure with its own greeting.

Private state: a counter#

Closures can keep data private: nothing outside can reach the variable directly, only through the functions you return:

JavaScript
function createCounter() {
  let count = 0;
  return {
    increment() {
      count++;
      return count;
    },
    reset() {
      count = 0;
    },
    get value() {
      return count;
    },
  };
}

const clicks = createCounter();
clicks.increment();
clicks.increment();
console.log(clicks.value);

const views = createCounter();
views.increment();
console.log(views.value, clicks.value);

console.log(clicks.count);
Output
2
1 2
undefined

Each counter has its own count, and there is no way to set it to an invalid value from outside. (Classes offer a similar feature with private #fields; see Classes.)

Function factories#

A function that builds other functions, customised by its arguments:

JavaScript
const multiplier = (factor) => (n) => n * factor;

const double = multiplier(2);
const triple = multiplier(3);
const addGst = multiplier(1.18);

console.log(double(10), triple(10), addGst(100));
Output
20 30 118

Closures capture variables, not values#

A closure keeps a live link to the variable, so it sees later changes:

JavaScript
let rate = 0.05;
const interest = (amount) => amount * rate;

console.log(interest(1000));
rate = 0.08;
console.log(interest(1000));
Output
50
80

The classic loop question#

JavaScript
const withVar = [];
for (var i = 1; i <= 3; i++) {
  withVar.push(() => i);
}

const withLet = [];
for (let j = 1; j <= 3; j++) {
  withLet.push(() => j);
}

console.log(withVar.map((fn) => fn()));
console.log(withLet.map((fn) => fn()));
Output
[ 4, 4, 4 ]
[ 1, 2, 3 ]

With var, there is one variable i for the whole loop; all three closures share it, and by the time they run it is 4. With let, each iteration gets a new j, so each closure remembers its own value.

Practical uses#

Remembering expensive results (memoization)#

JavaScript
function memoize(fn) {
  const cache = new Map();
  return (n) => {
    if (cache.has(n)) return `${cache.get(n)} (cached)`;
    const result = fn(n);
    cache.set(n, result);
    return result;
  };
}

const slowSquare = (n) => n * n;
const fastSquare = memoize(slowSquare);

console.log(fastSquare(12));
console.log(fastSquare(12));
Output
144
144 (cached)

Event handlers#

Event handlers in web pages are closures over the variables around them:

HTML
<button id="a">Button A</button>
<button id="b">Button B</button>
<p id="log">Click a button.</p>
JavaScript
function setUp(id) {
  let clicks = 0;   // each button gets its own counter
  document.getElementById(id).addEventListener("click", () => {
    clicks++;
    document.getElementById("log").textContent = `${id} clicked ${clicks} times`;
  });
}
setUp("a");
setUp("b");
Result

Practice#

Practice exercise

Write createBankAccount(initial) that keeps the balance private and returns deposit(amount), withdraw(amount) (which refuses to go below zero and returns a message) and getBalance().

Show solution
JavaScript
function createBankAccount(initial) {
  let balance = initial;
  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount > balance) return "Insufficient funds";
      balance -= amount;
      return balance;
    },
    getBalance() {
      return balance;
    },
  };
}

const account = createBankAccount(500);
console.log(account.deposit(200));
console.log(account.withdraw(1000));
console.log(account.withdraw(300));
console.log(account.getBalance());
Output
700
Insufficient funds
400
400

Key takeaway#

Finished this lesson?