IntermediateJavaScript · Lesson 3 of 10

Closures & `this`

Functions that remember their surroundings, private state, and how `this` is decided.

A closure is a function that remembers the variables from where it was created, even after that outer function has finished. This gives you private state: in createCounter, nothing outside can change count except through the returned functions.

Closures are everywhere: event handlers, callbacks, setTimeout, and helper factories such as debounce or once.

this depends on how a function is called, not where it is written. Called as obj.method(), this is obj; passed around as a plain callback, it loses that. Arrow functions don't have their own this — they use the surrounding one — which is why they are handy inside methods. fn.bind(obj) fixes this permanently.

closures.jsJavaScript
"use strict"; // modules and classes are always strict; plain scripts opt in like this

function createCounter(start = 0) {
  let count = start;                       // private: only the functions below can reach it
  return {
    increment: () => ++count,
    reset: () => (count = start),
    get value() { return count; },
  };
}

const visitors = createCounter();
visitors.increment();
visitors.increment();
console.log(visitors.value, visitors.count);   // 2 undefined

function once(fn) {
  let done = false;
  let result;
  return (...args) => {
    if (!done) {
      done = true;
      result = fn(...args);
    }
    return result;
  };
}
const init = once(() => { console.log("initialising..."); return 42; });
console.log(init(), init());              // logs "initialising..." once, then 42 42

const teacher = {
  name: "Ms. Lyimo",
  students: ["Amina", "Neema"],
  greetAll() {
    // Arrow callback: uses greetAll's `this`, so this.name works
    return this.students.map((s) => `${this.name} greets ${s}`);
  },
  introduce() {
    return `I am ${this.name}`;
  },
};
console.log(teacher.greetAll());

const loose = teacher.introduce;          // detached from the object
try {
  console.log(loose());
} catch (err) {
  console.log("Lost this:", err.constructor.name);
}
const bound = teacher.introduce.bind(teacher);
console.log(bound());                      // I am Ms. Lyimo
Runs in your browser · JavaScript

Key points

  • Closures remember outer variables — use them for private state and helper factories.
  • this is set by the call site: obj.method() → obj.
  • Arrow functions inherit this; bind fixes it for passed-around methods.

Exercise

Write createBankAccount(owner) that returns deposit, withdraw and getBalance functions with the balance kept private, refusing withdrawals larger than the balance. Then write makeMultiplier(n) that returns a function multiplying its input by n.

Show solution

Try the exercise yourself first — then compare your approach with this one.

balance lives only inside createBankAccount, so nothing outside can change it except through the returned functions — real private state. makeMultiplier returns a function that remembers its own n.

closures-solution.jsJavaScript
function createBankAccount(owner) {
  let balance = 0;
  return {
    deposit(amount) {
      if (amount <= 0) throw new RangeError("Deposit must be positive");
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount > balance) throw new RangeError(`${owner} has only ${balance}`);
      balance -= amount;
      return balance;
    },
    getBalance: () => balance,
  };
}

const account = createBankAccount("Amina");
account.deposit(50000);
account.withdraw(20000);
console.log(account.getBalance(), account.balance);   // 30000 undefined

try {
  account.withdraw(100000);
} catch (err) {
  console.log(err.message);                        // Amina has only 30000
}

const makeMultiplier = (n) => (x) => x * n;
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(21), triple(5));                // 42 15
Runs in your browser · JavaScript

Check your understanding

  1. What is a closure?

  2. For obj.method(), what is this inside method?

  3. Why are arrow functions handy inside methods for callbacks like map?

  4. What does fn.bind(obj) return?

Ask AI