What is a closure in JavaScript?
The short answer
A closure is when a function remembers and can access variables from its outer (parent) function, even after the outer function has finished executing. This happens because JavaScript functions carry a reference to the scope they were created in.
How closures work
To understand closures, you first need to understand how JavaScript handles scope.
When you write a function inside another function, the inner function has access to three things:
- Its own variables
- Variables from the outer function
- Global variables
This is called the scope chain. Now here is the important part — even after the outer function finishes running and is removed from the call stack, the inner function still holds on to the variables from the outer function. It does not copy those variables, it keeps a live reference to them.
This behavior is what we call a closure.
function createCounter() { let count = 0; return function increment() { count = count + 1; return count; };}const counter = createCounter();console.log(counter()); // 1console.log(counter()); // 2console.log(counter()); // 3Let me walk you through what is happening here step by step.
When we call createCounter(), it creates a variable count and sets it to 0. Then it returns the increment function. At this point, createCounter is done executing — its execution context is removed from the call stack.
Now, when we call counter() (which is the increment function), it still has access to count. It updates count from 0 to 1 and returns 1. The next time we call counter(), it updates count from 1 to 2. The variable count stays alive because the increment function is still holding a reference to it.
This is the closure — increment has "closed over" the variable count.
Why closures are useful
Closures solve a very practical problem — they let you create private variables in JavaScript. JavaScript does not have a built-in way to make variables truly private (like private keyword in Java or C++), but closures give you a way to do it.
function createBankAccount(initialBalance) { let balance = initialBalance; return { deposit(amount) { balance = balance + amount; return balance; }, withdraw(amount) { if (amount > balance) { return 'Insufficient funds'; } balance = balance - amount; return balance; }, getBalance() { return balance; }, };}const account = createBankAccount(100);account.deposit(50); // 150account.withdraw(30); // 120account.getBalance(); // 120// There is no way to access `balance` directlyconsole.log(account.balance); // undefinedThe balance variable is not exposed on the returned object. The only way to interact with it is through the deposit, withdraw, and getBalance methods. This is data privacy through closures.
Closures in everyday code
You have probably used closures without even knowing it. Here are some patterns you will recognize.
Event handlers:
function setupButton(buttonId, message) { const button = document.getElementById(buttonId); button.addEventListener('click', function () { // This callback closes over `message` alert(message); });}setupButton('save-btn', 'Your work has been saved!');setupButton('delete-btn', 'Item deleted.');Each click handler remembers its own message because of closures. Without closures, both buttons would show the same message.
React hooks: Every time you use useState or useEffect, closures are at work behind the scenes.
function SearchBar() { const [query, setQuery] = useState(''); // This function closes over `query` and `setQuery` const handleChange = (event) => { setQuery(event.target.value); }; return <input value={query} onChange={handleChange} />;}The handleChange function has access to setQuery because of closures. Every render creates a new closure with the latest values.
The stale closure trap
This is the most common mistake candidates make with closures, and interviewers love to test it.
Common Pitfalls
A very common bug in React is the stale closure problem. When you use useEffect or setTimeout inside a component, the callback captures the value of state at the time it was created, not the latest value.
function Timer() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { // This always sees count as 0 // because it closed over the initial value console.log(count); setCount(count + 1); }, 1000); return () => clearInterval(id); }, []); return <p>{count}</p>;}The setInterval callback closes over count at the time the effect ran (which is 0). So count + 1 is always 0 + 1 = 1. The counter gets stuck at 1.
The fix is to use the functional form of the setter:
setCount((prevCount) => prevCount + 1);This way you are not relying on the closed-over value, you are getting the latest value from React directly.
The classic loop problem
This is another very popular interview question. Look at this code:
for (var i = 0; i < 3; i++) { setTimeout(function () { console.log(i); }, 1000);}// Output: 3, 3, 3 (not 0, 1, 2)All three functions close over the same i variable (because var is function-scoped, not block-scoped). By the time the setTimeout callbacks run, the loop has already finished and i is 3.
There are two common fixes:
Fix 1: Use let instead of var
for (let i = 0; i < 3; i++) { setTimeout(function () { console.log(i); }, 1000);}// Output: 0, 1, 2let is block-scoped, so each iteration of the loop gets its own i.
Fix 2: Create a new scope with an IIFE
for (var i = 0; i < 3; i++) { (function (j) { setTimeout(function () { console.log(j); }, 1000); })(i);}// Output: 0, 1, 2The IIFE creates a new scope for each iteration, capturing the current value of i as j.
Interview Tip
If the interviewer asks you about closures and loops, start with the let solution because it is the modern and clean approach. Then mention the IIFE approach to show you understand what is happening under the hood. This shows both practical knowledge and deeper understanding of how scoping and closures work together.
Why interviewers ask this
Closures are one of the foundational concepts in JavaScript. When an interviewer asks about closures, they are not just testing if you know the definition. They want to see if you understand how scope and memory work in JavaScript, if you can identify closure-related bugs (like stale closures in React or the loop problem), and if you use closures intentionally in your code for things like data privacy and function factories. A candidate who can explain closures clearly and walk through the gotchas demonstrates solid JavaScript fundamentals, which is exactly what companies are looking for.