Facebook Pixel

Closures in Callbacks and Event Handlers in JavaScript

Every callback is a closure. Here is how closures work in setTimeout, event listeners, and async callbacks.

Closures in Callbacks and Event Handlers in JavaScript

Every callback in JavaScript is a closure. The callback function closes over the variables from where it was defined. Understanding this explains timer behavior, event handler state, and async patterns.

Closures in setTimeout

const message = "hello"; setTimeout(() => { console.log(message); // "hello" (closed over) }, 1000); `` The arrow function closes over `message`. Even though `setTimeout` schedules it for later, the callback still has access to `message` via its lexical environment. ### The Loop Closure Bug ```js for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // logs 3, 3, 3 `` All callbacks share one `i` (var). Fix with `let`: ```js for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // logs 0, 1, 2 `` ### Closures in Event Listeners ```js function setupCounter(button) { let count = 0; button.addEventListener("click", () => { count++; button.textContent = `Clicked ${count}`; }); } setupCounter(document.querySelector("button")); `` The handler closes over `count` and `button`. Each click increments the closed-over `count`. ### Closures in `fetch` Callbacks ```js function fetchUser(userId) { const requestId = Math.random(); fetch(`/api/users/${userId}`) .then((res) => res.json()) .then((user) => { console.log(requestId, user); // requestId is closed over }); } `` The `.then` callback closes over `requestId`, which was defined in the outer function. ### Closures in `map`, `filter`, `reduce` ```js const multiplier = 3; const doubled = [1, 2, 3].map((n) => n * multiplier); // [3, 6, 9] `` The `map` callback closes over `multiplier`. ### Removing Event Listeners ```js function setup() { const handler = () => console.log("clicked"); button.addEventListener("click", handler); // later: button.removeEventListener("click", handler); // must be the same reference } `` To remove a listener, you need the same function reference. Anonymous functions cannot be removed. This is important for avoiding memory leaks. ### The Takeaway Every callback is a closure. `setTimeout`, event listeners, `fetch.then`, and `map`/`filter`/`reduce` callbacks all close over variables from their defining scope. The loop closure bug is the most famous example. Use `let` in loops and named functions for removable listeners.

Yes. Every callback function closes over the variables from where it was defined. setTimeout callbacks, event listeners, fetch.then, and map/filter/reduce callbacks are all closures.

Because the callback is a closure. It was defined in a scope where those variables existed, so it holds a reference to that lexical environment. When it runs later, it can still access them.

The listener function closes over variables from where it was defined. For example, a click handler can close over a count variable and increment it on each click. The closed-over state persists across events.

Because removeEventListener needs the same function reference that was passed to addEventListener. Anonymous functions create a new reference each time. Use a named function stored in a variable so you can pass the same reference to remove.

Use let instead of var in the for loop. let creates a fresh binding per iteration, so each callback closes over its own i. With var, all callbacks share one i, which is the final value by the time they run.

Ready to master React completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.