Agentic AI with Java: Live Cohort
JavaScriptFunctions and Scope

Closures and this

Capture surrounding variables and understand how method calls bind this.

A closure is a function that keeps access to bindings in the scope where it was created, even after that scope has finished running. This makes private state possible without a class.

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

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

Every call to makeCounter() creates a separate count. Closures capture the binding, so later changes are visible. This is also why a callback can accidentally retain a large object longer than necessary.

The meaning of this

For a normal function called as an object method, this is the object before the dot. It depends on how the function is called, not where it was written.

const account = {
  owner: "Asha",
  describe() { return `Account: ${this.owner}`; },
};
console.log(account.describe()); // Account: Asha

const detached = account.describe;
// detached() has no account receiver in a module; this is undefined.
const bound = account.describe.bind(account);
console.log(bound()); // Account: Asha

Arrow functions do not create their own this; they use the surrounding one. That makes them useful inside a method callback, but not as a method that needs its own receiver. call, apply, and bind explicitly choose a receiver for a normal function; bind returns a new function.

Why the counter remembers

makeCounter() returns an arrow function that refers to count. Normally a local binding disappears from your reach when its function returns. Here, the returned function still needs it, so the binding remains reachable through that closure. A second call to makeCounter() creates a second independent binding. The counter is private because callers have no direct reference to count; they can only call the returned function.

const a = makeCounter();
const b = makeCounter();
console.log(a(), a(), b(), a()); // 1 2 1 3

The arrow function in this example does not use this. If it did, it would inherit this from the place where it was created. A normal method, by contrast, receives this from the call expression account.describe(). Moving the method into detached removes that receiver. In an ES module, calling detached() leaves this as undefined, so reading this.owner fails.

This distinction matters for event handlers and timers. If a method needs its object, call it through that object, wrap it in an arrow such as () => account.describe(), or bind it once. Do not turn every method into an arrow field without a reason: each instance then creates its own function rather than sharing one prototype method.

Practice: Create two counters with makeCounter() and alternate calls. Then detach account.describe and repair it with bind().

Check: Separate counters should each start at 1. account.describe.bind(account) returns a new callable that keeps account as its receiver even when stored elsewhere.

Closures capture bindings

A closure is a function together with access to variables in the lexical scope where it was created. The function keeps access to those bindings after the outer function returns. It captures the binding, not a frozen snapshot of its current value, which is why a counter can update private state between calls.

Closures are useful for callbacks, event handlers, factories, and maintaining state without exposing it directly. They can also keep data alive: if a long-lived callback closes over a large object, that object remains reachable until the callback is removed or discarded.

this depends on the call

For an ordinary function, this is determined by how the function is called. object.method() supplies object as the receiver; storing the method in another variable and calling it as a bare function loses that receiver. In strict mode, an unbound ordinary function has this === undefined (older non-strict scripts may substitute the global object).

Arrow functions do not create their own this; they use the surrounding one. This is convenient for a callback inside an object method, but an arrow function is not a replacement for every method. Use an ordinary method when callers need this to refer to the receiver.

bind() returns a new function with a fixed receiver and, optionally, leading arguments. call() and apply() invoke a function immediately with a chosen receiver; apply() takes arguments as an array-like value. These tools are useful when a callback API detaches a method from its object.

Written By: Shiva Srivastava

How is this guide?

Last updated on