October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk4 min

JavaScript Closures: How Functions Keep Access to Their Scope

A practical guide to JavaScript closures: how lexical scope works, why returned functions retain access to outer bindings, and how closures handle shared state and loop callbacks.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A JavaScript closure lets a function keep using bindings from the scope where it was created—even after that outer function has finished. In the example below, makeGreeter() returns a function that still knows the value of name:

function makeGreeter(name) {
  return function () {
    return `Hello, ${name}!`;
  };
}

const greet = makeGreeter("Mina");
console.log(greet()); // "Hello, Mina!"

The returned function works because its lexical environment remains available to it. To understand why, start with how JavaScript decides which variable a function uses.

As an Amazon Associate I earn from qualifying purchases.

How lexical scope determines a variable

JavaScript uses lexical scope: a function resolves a name based on where the function is defined in the source code, not on the place from which it is later called. If a function refers to a binding in an enclosing scope, it can access that binding.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const message = "outside";

function showMessage() {
  console.log(message);
}

function run() {
  const message = "inside";
  showMessage();
}

run(); // "outside"

showMessage was defined in the scope containing the outer message, so calling it from run does not redirect its lookup to run‘s local binding. The chain of surrounding scopes associated with where a function is defined is its lexical environment.

What a closure retains

MDN defines a closure as “the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).” In practice, a closure is a function together with access to the outer bindings it can resolve. A nested function can therefore use an outer binding after the outer function has returned.

A closure does not freeze or copy every outer variable as a snapshot. It retains access to bindings, so if a binding changes, a closure that reads it can observe the updated value.

function makeReader() {
  let status = "waiting";

  return function () {
    return status;
  };
}

const readStatus = makeReader();
console.log(readStatus()); // "waiting"

When makeReader returns, readStatus can still read the status binding from the environment where the inner function was created.

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

Function factories create separate environments

A function factory returns a function whose behavior depends on bindings established by that particular call. Calling the factory again creates a separate environment, even though each returned function uses the same function-body structure.

function makeAdder(x) {
  return function (value) {
    return value + x;
  };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

add5 resolves x in the environment created by makeAdder(5); add10 resolves it in the distinct environment created by makeAdder(10). This is why one factory can produce functions with different retained values.

Closures can share private state

When several functions are created within one invocation, they can share access to the same binding. This makes it possible to keep state inside a function and expose only operations that read or update it.

function makeCounter() {
  let count = 0;

  return {
    increment() {
      count += 1;
      return count;
    },
    current() {
      return count;
    }
  };
}

const counter = makeCounter();
console.log(counter.increment()); // 1
console.log(counter.current());   // 1

Both methods close over the same count binding. Code using counter can call the provided methods but cannot access count directly by that name.

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

Where closures appear in everyday JavaScript

  • Callbacks: A callback can use bindings from the scope in which it was defined, even when another part of the program calls it later.
  • Function factories: A factory can return specialized functions, as makeAdder does.
  • State associated with operations: Multiple functions can share an environment that holds state, as the counter methods do.
  • Nested functions: A nested function can read outer bindings whether or not it is returned.

Closures are not limited to anonymous functions or functions returned from other functions. The essential point is access to the function’s lexical environment.

Why callbacks in a var loop can all see the same value

var is function-scoped rather than block-scoped. In a loop that uses one var binding, callbacks created during the loop can all refer to that shared binding. If the callbacks run after the loop has finished, they can each read its final value.

const callbacks = [];

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

console.log(callbacks.map(callback => callback())); // [3, 3, 3]

The callbacks do not each retain a separate snapshot of i. They resolve the same function-scoped binding, whose value is 3 after the loop ends.

Use let for a per-iteration binding

A let declaration in a loop creates a separate binding for each iteration, so each callback can resolve the value from its own iteration.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const callbacks = [];

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

console.log(callbacks.map(callback => callback())); // [0, 1, 2]

Use for...of when iterating values

If callbacks should use each item directly, for...of with a block-scoped binding is another clear option.

const callbacks = [];

for (const value of ["a", "b", "c"]) {
  callbacks.push(function () {
    return value;
  });
}

console.log(callbacks.map(callback => callback())); // ["a", "b", "c"]

MDN also documents forEach() as an alternative for this pattern. These are different ways of structuring the bindings callbacks use; choose the form that makes the iteration and intended values easiest to follow.

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

JavaScript closures cheat sheet

Question Quick answer
What is a closure? A function together with access to bindings in its lexical environment.
Where does a function look for outer names? In the scopes around where it was defined, not where it is called.
Does a closure copy a variable’s value? No. It retains access to the binding the function resolves.
Can two closures share state? Yes. Functions created in the same environment can access the same binding.
Why can var loop callbacks return the same value? They may all resolve one shared function-scoped binding.
How can a loop give callbacks distinct values? Use a block-scoped loop declaration such as let, or an iteration form such as for...of or forEach().

For a deeper reference, see MDN’s JavaScript guide to closures.

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.

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

Leave a Reply

Your email address will not be published. Required fields are marked *

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

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.