Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

A JavaScript function can keep access to variables from the scope where it was created—even after that outer function has finished running. That continued access is a closure. It is often described as a function “remembering” values, but it is more accurate to say the function retains access to the surrounding lexical bindings; it does not necessarily keep a frozen copy of every value.

What is a JavaScript closure?

MDN defines a closure as “the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).” In practical terms, a function can use variables from the scope in which it was defined, even when it runs later or somewhere else in the program. MDN’s closures guide explains the relationship between functions and lexical environments.

For example, a function returned from another function can still access a variable declared by its outer function. The outer call may be over, but the returned function retains access to the binding it needs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How does a closure keep state?

Consider a counter factory:

function makeCounter() {
  let count = 0;
  return function () {
    count += 1;
    return count;
  };
}

const next = makeCounter();
next(); // 1
next(); // 2

When makeCounter() runs, it creates the count binding and returns an inner function. Each time next() is called, that inner function can still access and update the same binding. The outer function does not need to be running for this to work.

Calling makeCounter() again creates a new call environment and a new count binding. The counter returned by that call is independent of next:

const nextA = makeCounter();
const nextB = makeCounter();

nextA(); // 1
nextA(); // 2
nextB(); // 1

“Remembering” is a useful shorthand, but it can mislead: the closure gives the function continued access to a binding, not necessarily a snapshot of its value. If the binding changes, later calls can observe the change.

How can several functions share one closure?

Functions created during the same outer call can share access to the same lexical environment. That makes it possible to keep a piece of state alongside a small set of operations that use it. MDN’s counter example exposes increment, decrement, and read operations that all work with shared private state.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Here is the same pattern in a compact form:

function makeCounter() {
  let count = 0;

  return {
    increment() { count += 1; },
    decrement() { count -= 1; },
    value() { return count; }
  };
}

const counter = makeCounter();
counter.increment();
counter.increment();
counter.value(); // 2

The returned methods close over the same count binding, so each operation sees updates made by the others. Code outside the factory cannot access count by that name; it can interact with the value through the returned methods. This is useful organization for state and behavior, but a closure should not be treated as a complete security boundary.

Why do loop callbacks sometimes all use the last value?

A common surprise occurs when callbacks created in a loop use a var variable and run after the loop has advanced. Because var is function-scoped, the callbacks can all close over the same binding. The loop keeps changing that binding, so delayed callbacks may all observe its final value. The cause is which binding they share, not a special interaction between closures and timers.

for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 0);
}

In this example, the callbacks refer to the same i. By the time they run, the loop has completed and i is 3, so each callback logs 3.

Use a block-scoped loop variable when each callback should have its own iteration value:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 0);
}

With let in the loop initializer, callbacks in this pattern access a per-iteration binding and log 0, 1, and 2. MDN’s for reference describes loop lexical declarations and the shared-binding pitfall.

Other modern iteration forms can make the intended value clearer, depending on the task:

for (const item of items) {
  setTimeout(() => console.log(item), 0);
}

items.forEach((item) => {
  setTimeout(() => console.log(item), 0);
});

Choose the form that matches the surrounding code; the key is that each callback should access the intended iteration value rather than one changing function-scoped binding.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When are closures useful?

  • Callbacks: A callback can use values from the context where it was created, even when invoked later.
  • Event-driven code: A handler can use relevant surrounding state when an event occurs.
  • Small stateful APIs: Several functions can operate on one shared binding while keeping that binding out of the surrounding scope.

To recognize a closure, look for a function that uses a variable declared in an enclosing scope. Then ask whether it is using the same binding as other functions, or a separate binding created for its own call or loop iteration. That distinction explains both useful shared state and many callback surprises.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.