Lesson 3 of 80
The hook that lets a component remember a value between renders — by closing over a slot React keeps aside for it. A closure, wearing a React hat.
16 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 4
What's the fix for the stuck-counter bug above?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A component function re-runs from scratch on every render — so where does state "live" between one call and the next? Not in a normal variable; those reset every time.
useState works because React keeps a slot of memory outside the function, tied to that specific component instance. Calling useState(0) hands you the current value in that slot, plus a setter that tells React "update the slot, then re-render me." This is a closure, the exact idea from the JavaScript track — useState closes over React's internal state cell the same way any closure closes over a variable from its enclosing scope.
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 🐛 always resolves to 1
}, 1000);
return () => clearInterval(id);
}, []); // empty deps — this effect, and its closure, only ever runs once
return <p>{count}</p>;
}
This counter gets stuck at 1 forever. The interval callback was created once, on the first render, and it closed over that render's count — which was 0. Every tick, it computes setCount(0 + 1), over and over. It never sees 1, 2, 3 — it only ever saw 0.
Render 1 creates the interval's callback — it closes over count as it was on THIS render: 0.
setCount(1) schedules a re-render; count inside the function you're currently running stays whatever it was when this render started — that's just how closures work.setCount three times in one click handler triggers one re-render, not three — React collects updates and applies them together.useState's initial value is only used on the very first render. useState(expensiveCalculation()) re-runs that calculation on every render even though the result is thrown away every time after the first — pass a function (useState(() => expensiveCalculation())) to run it once, lazily.state.items.push(x) mutates in place and React won't notice; setState([...state.items, x]) creates a new array React can detect changed.The stale-closure bug is one of the most common real React bugs and one of the most common interview questions, because it tests two things at once: do you understand closures (the JavaScript fundamental), and do you understand that a component re-render is a fresh function call, not a mutation of the same one.