Closures, Loop Bugs, and Stale React Values
How do closures retain lexical scope in JavaScript, and where do you use them in real code? Walk me through the classic loop bug and a stale-value bug in React.
what they're testing: The interviewer is testing whether you can reason about a callback's captured environment across time and debug the bugs that follow from it.
A closure lets a function keep using the bindings that were in scope where the function was created, even after the surrounding call returns. It does not freeze a copy of every value. That is useful for carrying context into event handlers, timers, and middleware, as well as for factory functions and private module state.
The classic loop bug comes from var. If a loop registers three callbacks with var i, they share one function-scoped binding. When they run after the loop, they all see 3. Declaring i with let gives each iteration its own binding; a factory function can also capture the current value in a new call.
React exposes the same rule across renders. Each render creates new bindings and callbacks. Starting from zero, an interval created by useEffect(..., []) can retain the first render's count, so setCount(count + 1) keeps requesting 1. The callback is following normal JavaScript closure behavior. When the update depends only on previous state, use setCount(c => c + 1). When an Effect reads a reactive value, include it as a dependency so React re-synchronizes the Effect.
Why it lands
- Uses bindings, not copied values, as the central mental model.
- Connects everyday callbacks and private state to practical closure use.
- Explains why every `var` callback sees the final loop value.
- Treats stale React state as normal JavaScript across separate renders.
Where people slip
- "A closure copies outer variables when the function is created." Closures retain access to bindings, not frozen copies.
- "The timeout runs too late, so JavaScript loses the index." The callbacks still share the one `var` binding.
- "React state is mutable, so the interval should see the latest value." Each render gives its callbacks a particular state snapshot.
- "Just disable the dependency lint rule." That preserves the old callback and hides a real synchronization bug.
If they push further
Does a closure capture a variable by reference or by value?
It retains access to the lexical binding. Closures sharing one binding observe its changes, while separate function calls or React renders create separate bindings.
Why does `let` fix the loop bug?
A `let` declared in the loop header gets a new binding for each iteration. Each callback closes over its own iteration's binding instead of one shared function-scoped variable.
When should a stale React callback use an updater instead of a dependency?
Use an updater when the callback only needs previous state to calculate the next state. Use a dependency when a changed reactive value should re-synchronize the Effect; use an Effect Event when Effect logic needs the latest value without reacting to it.
Sources
- MDN: Closures ↗developer.mozilla.org
- React: State as a Snapshot ↗react.dev
- React: Removing Effect Dependencies ↗react.dev
Now answer it yourself.
Reading a strong answer is easy. Producing one under pressure is the skill the interview tests. Gapmap grades your answer against the same bar an interviewer would.
beta
The interviewer part is in the works.
The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.