Skip to main content

Menu

System onlineAI Tutorials / 2026

AI Tutorials
Knowledge search

Recent · Popular · Tutorials

  • dart3 tutorials
  • java1 tutorial
  • Javascript3 tutorials
  • Node.js Introduction2 tutorials
  • dartdart · General · In depth
  • JavascriptJavascript · Advanced · In depth
  • JavascriptJavascript · Advanced · Mid
  • JavascriptJavascript · Advanced · Short

Find a tutorial

Tell the assistant what you want to learn. It suggests published chapters.

For example: “I want to learn Rust ownership from the start.” The conversation is kept only in this tab.

0 / 2,000 characters

Enter to send, Shift+Enter for a new line. Do not include personal information.

Chapter 5

Closures

This chapter assumes you are comfortable with function declarations vs. expressions, lexical scope, `let`/`const`/`var`, and the difference between values and references from earlier chapters.
Level: AdvancedLength: Mid2 min read

Read at another depth

Closures

This chapter assumes you are comfortable with function declarations vs. expressions, lexical scope, let/const/var, and the difference between values and references from earlier chapters.

What a closure actually is

A closure is the combination of a function and the lexical environment it was created in. When a function is defined, it captures a reference to the enclosing scope's variable bindings. When the function later executes—even long after the enclosing function has returned—those bindings remain alive. The function "closes over" its creation scope; hence the name.

Crucially, the closure holds references to the bindings themselves, not snapshots of their values. Mutations are shared across all closures over the same scope.

Code example in javascript
function makeCounter() {
  let count = 0;
  return function () {
    count += 1;
    return count;
  };
}

const next = makeCounter();
console.log(next()); // 1
console.log(next()); // 2

count outlives makeCounter's return because the returned function references it. This is also the canonical module pattern: private state, exposed only through a controlled interface.

Shared environments and loop captures

Two closures created in the same call share one environment:

Code example in javascript
function pair() {
  let n = 0;
  return {
    inc: () => ++n,
    get: () => n,
  };
}
const { inc, get } = pair();
inc();
console.log(get()); // 1 — both arrows close over the same n

With var, loop-scoped closures share a single function-scoped binding, a classic bug. let creates a fresh binding per iteration:

Code example in javascript
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)); // 3, 3, 3
for (let j = 0; j < 3; j++) setTimeout(() => console.log(j)); // 0, 1, 2

Gotchas

  • Retention: every captured binding stays reachable as long as the closure is. Closures over large objects (DOM nodes, buffers) are a leading cause of leaks in long-lived callbacks, event handlers, and memoization caches.
  • Eval and with: either can force an engine to deoptimize the enclosing scope into a slow, object-like environment. Avoid them inside hot closures.
  • False snapshot expectation: capturing a variable captures the binding; reassignment elsewhere is visible. To freeze a value, copy it into a new const in an inner scope (an IIFE, or a let loop body).
  • Per-call state: each invocation of the outer function creates a fresh environment. makeCounter() twice yields independent counters—useful, but surprising if you expected a singleton.

Decision criteria

Use closures for encapsulated state, partial application, and once-style guards. Prefer classes or plain objects with methods when state is large, mutable in many ways, or needs identity/subtyping. If a closure will be retained indefinitely, capture only what you need, and null out large references when done.

caption: A closure keeps its creation environment alive after the outer function returns. alt: Flow from calling makeCounter, creating an environment with count, returning the inner function, and the environment surviving via the closure reference.

Exercise

Why does for (var i...) setTimeout(() => console.log(i)) log 3 three times, while let logs 0, 1, 2?

Show answer

var declares one function-scoped binding shared by all the closures; by the time the timeouts run, the loop has finished and i is 3. let creates a fresh binding per iteration, so each closure captures its own value.

Exercise

How would you cap memory retained by a closure that wraps a large buffer?

Show answer

Capture only derived small values where possible, and explicitly release the large reference (set the variable holding it to null) once the work is done, so garbage collection can reclaim the buffer while the closure lives on.

Spotted a mistake, or code that does not work?

Report an issue

Chapter: Closures

0 / 2,000 characters

Plain text, for example: the second code sample does not compile. Do not include personal information.

You can't do that