skip to content

Handlers built inside a loop all see the final value — what are the two ways to give each its own?

level: middleimportance: must knowfreq 56%

answer

  1. one binding per pass
  2. fresh variable, not fresh assignment
  3. declare the copy inside the body
  4. a parameter is a new binding per call
  5. loop form guarantee or explicit copy

basics

~20 s

Either use a loop form that creates a fresh binding for each pass, so every closure captures a different variable, or copy the value inside the loop body into a local declared there and capture that copy instead.

solid answer

~40 s

Both repairs produce the same end state — **one binding per pass instead of one binding per loop** — and they differ only in who creates it. The first leans on the loop form: some loop constructs give every iteration its own variable, so the closures never shared anything to begin with. The second does it by hand: declare a local **inside the loop body**, initialise it from the loop variable while that pass is still current, and have the closure name the local. Passing the value into a small factory that returns the closure is this second repair in different clothes, because a parameter is a fresh binding created by each call. What matters in review is where the name is *declared*, not where it is assigned.

code

pseudocode · 12 lines
pseudocode
// broken: index is one binding for the whole loop
index = 0
while index < count(rows):
    handlers.add(function() { show(index) })
    index = index + 1

// fixed: rowIndex is declared in the body, so each pass creates one
index = 0
while index < count(rows):
    rowIndex = index
    handlers.add(function() { show(rowIndex) })
    index = index + 1

go deeper

for a junior

Remember the practical move: declare a new variable inside the loop body, set it from the current value, and let the closure use that new name.

for a middle

Explain why both repairs are the same thing — one binding per pass — and why the declaration site, not the assignment, is what decides how many bindings exist.

for a senior

Argue for the repair that survives a refactor, and say what you would put in a review checklist so the shape does not come back in another loop.

for a principal

Weigh relying on a loop-form guarantee against writing the copy explicitly: one is invisible in a diff, the other is noise that new readers can verify without knowing the construct.

## Both repairs do one thing The bug is that N closures share one variable. Every genuine fix therefore ends at the same place: **N bindings for N passes**, each initialised while its pass is current and never reassigned afterwards. Everything else — naming, style, which construct you reach for — is detail. Hold that sentence and you can judge any proposed fix in a review in a few seconds: count the bindings the code creates, not the assignments it executes. ## Fix one: let the loop form create the binding Some loop constructs create a fresh variable on every iteration. Under such a form the closure built on pass three closes over a variable that pass three owns, and the loop's advance to pass four writes a different variable entirely — so nothing the loop does afterwards is visible to the closure. - It requires **no change to the body at all**, which is why it is invisible in a diff and easy to lose in a later refactor. - Whether a loop form behaves this way differs between languages, and within one language different loop forms can differ from each other. It is a guarantee you have to know you have, not something to assume. - It does not help a variable you declared yourself above the loop; the loop form only governs the variable the loop itself introduces. ## Fix two: copy into a local declared in the body Declare a new name inside the loop body, initialise it from the current value, and have the closure name that new local: 1. The declaration is **executed on every pass**, so every pass creates a binding of its own. 2. The initialisation reads the loop variable while that pass is current, so the value is the right one. 3. Nothing reassigns the local afterwards, so the value the closure eventually reads is the value it was initialised with. This repair depends on nothing except where the declaration sits, which makes it the portable one and the one to prefer when you are not certain what the loop form guarantees. ## The factory form is the same repair Calling a small function that takes the value as a parameter and returns the closure looks like a third option and is not. A parameter is a binding created by the call, one per call, initialised from the argument. The call mechanism is simply writing the declaration for you. It earns its place when the closure is more than a line or two, or when several closures need the same captured value. ## Choosing between them | repair | what it depends on | when to prefer it | |---|---|---| | per-iteration binding from the loop form | a guarantee the loop construct makes | the guarantee is certain and the team reads code that way | | a local declared in the loop body | only where the declaration is written | anywhere you want the fix visible and self-evident | | a parameter of a factory called per pass | the call creating a parameter binding | the closure is long, or several share the captured value | A related consequence is worth knowing: where a language forbids capturing a variable that is later reassigned, this pitfall is rare — the rule forces you into the copy before the code will build at all. Where no such rule exists, the broken and the fixed shapes look almost identical on the page. ## What is not a fix - **Declaring the copy above the loop.** That is one binding reassigned per pass — the loop variable renamed, and the symptom is unchanged. - **Reordering or delaying the invocations.** The closures read the same variable whenever they run; sequence changes nothing. - **Freezing the variable after the loop.** The damage was done by the reassignments that already happened. - **Storing the closures keyed by row.** The key tells you which closure is which; it does not change what any of them reads. - **Capturing a container and an index into it.** If the index is the shared variable, you have moved the problem one level down without solving it. One caveat on the copy: a fresh binding fixes *which value* each closure sees. If that value is a reference to a mutable object that everything else also holds, later mutation of the object is still visible through every copy — the repair settles the binding, not the object.

  • Why does building the closure inside a function that takes the value as a parameter work?
    Each call creates a parameter binding of its own, initialised from the argument, and nothing reassigns it afterwards. The closure returned by that call captures that binding. It is the explicit copy again, written by the call mechanism instead of by a declaration in the body.
  • If the captured value is a reference to a mutable object, does the fresh binding still help?
    It fixes which object each closure points at, which is what the loop broke. It does not freeze the object: if every pass pointed at the same mutable object, later mutation is still visible through all of them. Copy the fields you need if that matters.
  • Which repair would you choose in an unfamiliar codebase?
    The declaration in the body, because it depends on nothing but where it is written. Relying on the loop form is correct where the guarantee is certain, but it leaves no visible trace in the code, so a later refactor to a different loop form silently reintroduces the bug.

saying these in an interview costs you the question

  • Says renaming the loop variable inside the body is enough
  • Declares the copy above the loop and calls the bug fixed
  • Assumes every loop form creates a fresh binding per pass
  • Believes marking the shared variable read-only afterwards repairs it
  • Thinks invoking the handlers earlier removes the need for a fix