0Pricing
JavaScript Academy · Lesson

How Closures Capture State

Understand the lexical environment of closures.

How Closures Capture State is a free JavaScript Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the JavaScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is a Closure

A closure is a function bundled together with references to the variables from the scope where it was defined. The inner function "remembers" those variables even after the outer function has returned.

function outer() {
  const message = "hello";
  function inner() {
    console.log(message); // sees outer variable
  }
  inner();
}
outer(); // "hello"

Returning the Inner Function

The magic appears when you return the inner function. It keeps access to the outer variable even though outer has already finished running.

function outer() {
  const message = "remembered";
  return function () {
    console.log(message);
  };
}
const fn = outer();
fn(); // "remembered"

Lexical Scope

Closures work because of lexical scoping: a function can access variables defined in the scopes that physically enclose it in the source code, not where it is called.

const x = 10;
function show() {
  console.log(x); // resolves where defined
}
function run() {
  const x = 99;
  show(); // still logs 10, not 99
}
run();

Capturing by Reference

A closure captures the variable itself, not a snapshot of its value. If the variable changes later, the closure sees the new value.

function make() {
  let count = 0;
  const get = () => count;
  count = 5; // changed after get defined
  return get;
}
console.log(make()()); // 5

Each Call Makes a New Scope

Every invocation of the outer function creates a fresh set of variables. Closures from different calls do not share state.

function counter() {
  let n = 0;
  return () => ++n;
}
const a = counter();
const b = counter();
console.log(a(), a()); // 1 2
console.log(b());      // 1 (independent)

Closures Over Parameters

Function parameters are also captured. This lets you build specialized functions from a general one.

function adder(x) {
  return (y) => x + y;
}
const add5 = adder(5);
console.log(add5(3));  // 8
console.log(add5(10)); // 15

The Classic Loop Trap

A famous bug: using var in a loop shares one variable across all closures. By the time they run, the loop is done and they all see the final value.

const fns = [];
for (var i = 0; i < 3; i++) {
  fns.push(() => i);
}
console.log(fns[0](), fns[1](), fns[2]()); // 3 3 3

Fixing It With let

let creates a new binding per iteration, so each closure captures its own i. This is the modern fix.

const fns = [];
for (let i = 0; i < 3; i++) {
  fns.push(() => i);
}
console.log(fns[0](), fns[1](), fns[2]()); // 0 1 2

Closures Keep Memory Alive

As long as a closure exists, the variables it captures cannot be garbage collected. This is usually fine but can cause leaks if you hold closures longer than needed.

function make() {
  const big = new Array(3).fill("data");
  return () => big.length;
}
const f = make();
console.log(f()); // 3 - big stays in memory via f

Closures Are Everywhere

Callbacks, event handlers, setTimeout, array methods, and promises all rely on closures to remember context. You use them constantly, often without noticing.

function greet(name) {
  return () => console.log("Hi " + name);
}
[greet("Ada"), greet("Bob")].forEach((fn) => fn());
// Hi Ada
// Hi Bob

Inspecting Captured State

Multiple closures from the same call share the same captured variables, so one can read what another writes.

function pair() {
  let value = 0;
  return {
    set: (v) => { value = v; },
    get: () => value
  };
}
const p = pair();
p.set(42);
console.log(p.get()); // 42

Quick Check

How closures capture state.

Recap

A closure is a function plus references to its defining scope. Thanks to lexical scoping it remembers outer variables after the outer function returns, capturing them by reference (not value). Each outer call makes an independent scope; use let to avoid the loop trap. Closures keep captured memory alive and power callbacks everywhere.

Frequently asked questions

Is the “How Closures Capture State” lesson free?

Yes — the full text of “How Closures Capture State” is free to read here on the web, and the JavaScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the JavaScript Academy course, upgrade to CoddyKit PRO.

What will I learn in “How Closures Capture State”?

Understand the lexical environment of closures. You practise JavaScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start JavaScript Academy?

No prior experience is required. JavaScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “How Closures Capture State” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this JavaScript Academy lesson?

Yes. Every JavaScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. How Closures Capture State
  2. Private Variables with Closures
  3. The Module Pattern (IIFE)
  4. Factory Functions
← Back to JavaScript Academy