JavaScript Interview Prep
Scope & Closures

Closures

The Variables a Function Never Forgets

LinkedIn Hook

You write a function inside another function. The inner one "remembers" variables from the outer one — even after the outer function has returned and its execution context is destroyed.

How? That's a closure. And it's the single most powerful concept in JavaScript.

Closures power React hooks. They power data privacy in factory functions. They're why the classic for loop with setTimeout breaks with var but works with let. They're behind every module pattern you've ever used.

But here's what most tutorials miss: closures aren't magic. They're a natural consequence of lexical scope — the rule that says "where you write a function determines what variables it can access."

In this lesson you'll learn the precise definition of a closure, build a private bank account with one, and fix the notorious for loop + setTimeout bug three different ways.

Read the full lesson -> [link]

#JavaScript #Closures #InterviewPrep #Frontend #CodingInterview #WebDevelopment #JSFundamentals


Closures thumbnail


What You'll Learn

  • The precise definition of a closure and why every JS function technically is one
  • How closures enable data privacy, factory functions, and event handlers that remember state
  • How to fix the classic for loop + setTimeout bug with let, IIFE, or the third-arg trick
  • Why closures capture references to variables, not snapshots of values

The Graduation Model

A closure is when a function "remembers" the variables from its lexical scope even after the outer function has returned and its execution context has been popped off the call stack.

Think of it this way: you graduate from university, but you still remember everything you learned there. The university (outer function) is "gone" from your daily life, but the knowledge (variables) stays with you (inner function). That's a closure.

Every function in JavaScript forms a closure. But the term is most meaningful when an inner function is returned or passed somewhere else and still accesses outer variables.

Basic Closure Example

function createCounter() {
  let count = 0; // this variable is "enclosed" in the closure

  return function () {
    count++;
    console.log(count);
  };
}

const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3

// count is NOT accessible from outside
// console.log(count); // ReferenceError

What's happening?

  1. createCounter() runs, creates count = 0, returns the inner function
  2. createCounter()'s execution context is destroyed (popped from call stack)
  3. But count is NOT garbage collected — the returned function still references it
  4. Every time we call counter(), it accesses and modifies the same count

Closure for Data Privacy

Closures are the foundation of data privacy in JavaScript. Before classes with #private fields, closures were the only way to create truly private variables.

function createBankAccount(initialBalance) {
  let balance = initialBalance; // private -- no one can touch this directly

  return {
    deposit(amount) {
      if (amount <= 0) throw new Error("Deposit must be positive");
      balance += amount;
      return `Deposited $${amount}. Balance: $${balance}`;
    },
    withdraw(amount) {
      if (amount > balance) throw new Error("Insufficient funds");
      balance -= amount;
      return `Withdrew $${amount}. Balance: $${balance}`;
    },
    getBalance() {
      return `$${balance}`;
    }
  };
}

const account = createBankAccount(100);
console.log(account.getBalance());   // "$100"
console.log(account.deposit(50));    // "Deposited $50. Balance: $150"
console.log(account.withdraw(30));   // "Withdrew $30. Balance: $120"
console.log(account.balance);        // undefined -- truly private!

Factory Functions with Closures

function createMultiplier(multiplier) {
  // multiplier is enclosed in the closure
  return function (number) {
    return number * multiplier;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
const tenTimes = createMultiplier(10);

console.log(double(5));   // 10
console.log(triple(5));   // 15
console.log(tenTimes(5)); // 50

// Each function has its OWN closure with its OWN multiplier value

The Classic Interview Problem — setTimeout in a Loop

// BROKEN: var -- single shared variable
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}
// Output: 3, 3, 3

// FIX #1: Use let (creates new scope per iteration)
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}
// Output: 0, 1, 2

// FIX #2: Use IIFE to create a closure (the old-school way)
for (var i = 0; i < 3; i++) {
  (function (j) {
    setTimeout(function () {
      console.log(j);
    }, 1000);
  })(i);
}
// Output: 0, 1, 2

// FIX #3: Use setTimeout's third parameter
for (var i = 0; i < 3; i++) {
  setTimeout(function (j) {
    console.log(j);
  }, 1000, i);
}
// Output: 0, 1, 2

Closures with Event Handlers

function setupButtons() {
  let clickCount = 0;

  document.getElementById("myBtn").addEventListener("click", function () {
    clickCount++;
    console.log(`Button clicked ${clickCount} times`);
  });

  // The event handler closes over clickCount
  // Every click increments the same clickCount variable
}

setupButtons();

Tricky Closure Example — Multiple Closures Share the Same Scope

function createFunctions() {
  let result = [];

  for (var i = 0; i < 3; i++) {
    result.push(function () {
      return i;
    });
  }

  return result;
}

const funcs = createFunctions();
console.log(funcs[0]()); // 3 (not 0!)
console.log(funcs[1]()); // 3 (not 1!)
console.log(funcs[2]()); // 3 (not 2!)
// All three closures share the SAME `i` variable (var is function-scoped)
// By the time any of them runs, the loop is done and i = 3

Closure Over References, Not Values

function example() {
  let x = 1;

  function inner() {
    console.log(x); // closes over the REFERENCE to x, not the value 1
  }

  x = 100; // modify x after inner is created

  inner(); // 100 (not 1!)
}

example();

This is critical to understand: closures capture variable references, not snapshots of values.

Closures visual 1


Common Mistakes

  • Believing closures capture values at creation time. They capture references — if the variable changes later, the closure sees the new value.
  • Using var inside a for loop with setTimeout and expecting each callback to see its own iteration value. All callbacks share the same i; use let (or an IIFE) to get a fresh binding per iteration.
  • Over-relying on closures for huge data structures in long-lived callbacks. The enclosed variables can't be garbage collected until the closure itself is unreachable — a common source of memory leaks.

Interview Questions

Q: What is a closure in JavaScript? Give a one-sentence definition.

A closure is a function bundled together with its lexical environment, so it retains access to the variables of its enclosing scope even after that scope has finished executing.

Q: How do closures enable data privacy?

By declaring variables inside an outer function and returning only methods that reference them. The outer function's variables are unreachable from the outside, but the returned methods can still read and write them via the closure — giving you private state with a controlled public API (the pattern behind createBankAccount).

Q: Fix the classic for loop + setTimeout problem. Explain 3 different approaches.

(1) Replace var with let — each iteration gets its own fresh block-scoped binding of i. (2) Wrap the body in an IIFE that takes i as a parameter, creating a new function scope per iteration. (3) Use setTimeout's third argument, which is passed to the callback on invocation, sidestepping the closure entirely.

Q: Do closures capture values or references?

Closures capture references to variables, not their values. If the variable changes after the closure is created but before it's called, the closure will see the updated value. This is why the for loop with var prints the final value of i for all iterations.

Q: Does every JavaScript function form a closure?

Technically yes — every function carries a reference to its outer lexical environment. In practice we only call it a "closure" when an inner function is returned, stored, or passed somewhere and continues to use those outer variables after the outer function has returned.


Quick Reference — Cheat Sheet

CLOSURES -- QUICK MAP

Definition:
  closure = function + its lexical environment
  -> keeps access to outer variables after outer returns

Capture rule:
  Captures REFERENCES, not snapshot values.
  If outer variable changes later, closure sees new value.

Top use cases:
  - Data privacy          (createBankAccount)
  - Factory functions     (createMultiplier)
  - Event handlers        (clickCount)
  - Module pattern        (IIFE)
  - Fixing for+setTimeout (let / IIFE / 3rd arg)

for-loop fix cheat:
  var  -> shared i, prints final value N times
  let  -> fresh i per iteration (preferred)
  IIFE -> pass i as parameter
  setTimeout(fn, ms, i) -> i passed to callback

Previous: Scope Chain -> The Variable Lookup Ladder Next: Block Scope vs Function Scope -> Where Your Actually Matters


This is Lesson 2.3 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.

On this page