Skip to content
Home JavaScript Closures in JavaScript

Closures in JavaScript

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Advanced JS → Topic 1 of 27
JavaScript closures explained — how functions remember their outer scope, practical closure patterns, the loop closure trap, and how closures enable modules and memoization.
⚙️ Intermediate — basic JavaScript knowledge assumed
In this tutorial, you'll learn
JavaScript closures explained — how functions remember their outer scope, practical closure patterns, the loop closure trap, and how closures enable modules and memoization.
  • A closure is a function bundled with its lexical environment — it carries its outer variables with it.
  • Closures survive the return of their outer function — the closed-over variables stay alive.
  • var in a for loop shares one variable across all loop iterations — use let to get a new binding per iteration.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

A closure is a function that retains access to variables from its outer (enclosing) scope even after the outer function has returned. The inner function closes over those variables — it carries them with it. Closures are how JavaScript implements private state, factory functions, memoization, and event handlers with context.

How Closures Work

Example · JAVASCRIPT
1234567891011121314151617181920212223242526
function makeCounter(start = 0) {
  let count = start;  // this variable lives in makeCounter's scope

  return {
    increment() { count++; },
    decrement() { count--; },
    value()     { return count; }
  };
  // makeCounter returns and its stack frame is gone
  // but count survives because the returned object closes over it
}

const counter = makeCounter(10);
counter.increment();
counter.increment();
counter.decrement();
console.log(counter.value()); // 11

// Two independent counters — each has its own count
const a = makeCounter(0);
const b = makeCounter(100);
a.increment();
a.increment();
b.increment();
console.log(a.value()); // 2
console.log(b.value()); // 101 — b has its own count
▶ Output
11
2
101

The Classic Loop Closure Trap

One of the most common JavaScript interview questions. The surprise: var-declared loop variables are shared across all closures created in the loop.

Example · JAVASCRIPT
123456789101112131415161718192021222324252627
// The trap — using var
const fns = [];
for (var i = 0; i < 3; i++) {
  fns.push(() => console.log(i));
}
fns[0](); // 3 — not 0!
fns[1](); // 3
fns[2](); // 3
// Why? var has function scope, not block scope.
// All three closures share the SAME i variable.
// By the time they run, i is already 3.

// Fix 1: use let (block-scoped — a new binding per iteration)
const fns2 = [];
for (let i = 0; i < 3; i++) {
  fns2.push(() => console.log(i));
}
fns2[0](); // 0 ✓
fns2[1](); // 1 ✓
fns2[2](); // 2 ✓

// Fix 2: IIFE to capture the current value (pre-ES6)
const fns3 = [];
for (var i = 0; i < 3; i++) {
  fns3.push(((capturedI) => () => console.log(capturedI))(i));
}
fns3[0](); // 0 ✓
▶ Output
3
3
3
0
1
2
0

Practical Pattern — Memoization

Closures are perfect for memoization: a function that caches its results so expensive calculations are only done once.

Example · JAVASCRIPT
1234567891011121314151617181920212223
function memoize(fn) {
  const cache = new Map();  // closed over by the returned function

  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      console.log(`Cache hit for ${key}`);
      return cache.get(key);
    }
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

const expensiveSqrt = memoize((n) => {
  console.log(`Computing sqrt(${n})...`);
  return Math.sqrt(n);
});

console.log(expensiveSqrt(16));  // Computing sqrt(16)... → 4
console.log(expensiveSqrt(16));  // Cache hit for [16]   → 4
console.log(expensiveSqrt(25));  // Computing sqrt(25)... → 5
▶ Output
Computing sqrt(16)...
4
Cache hit for [16]
4
Computing sqrt(25)...
5

🎯 Key Takeaways

  • A closure is a function bundled with its lexical environment — it carries its outer variables with it.
  • Closures survive the return of their outer function — the closed-over variables stay alive.
  • var in a for loop shares one variable across all loop iterations — use let to get a new binding per iteration.
  • Closures enable private state, factory functions, memoization, and partial application.
  • Every function in JavaScript is a closure — it closes over at least the global scope.

Interview Questions on This Topic

  • QWhat is a closure in JavaScript?
  • QWhy does a loop with var produce unexpected results when closures are involved?
  • QHow would you implement a counter using closures?

Frequently Asked Questions

Do closures cause memory leaks?

They can. If a closure holds a reference to a large object and the closure itself is kept alive (e.g., as an event listener that is never removed), the large object cannot be garbage-collected. The fix is to remove event listeners when they are no longer needed, or to break the reference by setting variables to null.

What is the difference between a closure and a callback?

A callback is a function passed as an argument. A closure is a function that remembers its outer scope. Most callbacks are closures — when you pass an arrow function to addEventListener or setTimeout, it typically captures variables from the surrounding scope, making it a closure. They are not mutually exclusive.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

Next →Promises in JavaScript
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged