skip to content

Some languages refuse to capture a local that is ever reassigned — what does that restriction buy them?

level: middleimportance: nice to knowfreq 34%

answer

  1. if it never changes, both models agree
  2. the rule bans assignment, not mutation
  3. no reassignment, no shared cell
  4. sharing becomes a declared container
  5. syntactic check rejects safe programs too

basics

~20 s

Unambiguous capture. With no reassignment possible, copying the value and holding the binding behave identically, so the language can copy, needs no shared cell, and removes the trap of a closure reporting a value written after it was created.

solid answer

~40 s

The rule forbids exactly one thing: assigning to a local that some closure captures. With that gone, the two capture models collapse into one — a copy and a shared binding can never be told apart, because nothing ever writes to the variable after capture. Three things follow. The implementation can copy the value and skip the cell and the indirection. The reader can tell what a deferred closure will report by looking at the capture site. And mutable state shared between a closure and its creator has to be declared as an explicit container, so the sharing is written down instead of hiding in a plain-looking local. The cost is ceremony for the cases that genuinely need sharing, and a rule checked syntactically, so some provably harmless programs are rejected too.

go deeper

for a junior

Know that some languages only let a closure capture a local that is never reassigned, and that this is a rule about the name rather than about the data.

for a middle

Explain why the restriction collapses the two capture models into one, what it lets the implementation skip, and how shared mutable state is still expressed.

for a senior

Argue the trade honestly: accidental sharing prevented, against ceremony and a syntactic check that refuses some harmless programs.

for a principal

Treat it as a design position on whether shared mutable state should be creatable by accident, and apply the same position through conventions in a language that does not enforce it.

## What the rule actually forbids The restriction is narrower than it sounds. It says: a local variable that a closure captures must be assigned once and never assigned again. It does **not** say the captured data is unchangeable. A variable that holds a handle to a mutable record satisfies the rule perfectly, and every field in that record may still be written by anyone who can reach it. The rule is about the **name**, not about the world the name reaches. That distinction is where candidates most often go wrong, in both directions: treating the rule as a guarantee of immutability, or dismissing it as pointless because mutation still gets through. ## What it buys - **One capture semantics instead of two.** If the variable can never be written after capture, copying the value and holding the binding produce identical observable behaviour, forever. The language no longer has to specify which it does, and the programmer no longer has to know. - **No shared cell.** Because a copy suffices, there is no allocation to make and no indirection on each access. The implementation gets the cheap layout in every case rather than as an optimisation. - **A deferred closure with no surprises.** The class of bug where a queued function reports a value written long after it was captured simply cannot be expressed. Whatever the capture site read is what the body will see. - **Sharing that is visible.** When a closure and its creator genuinely need to share a changing value, the programmer must put an explicit container in the variable and write through it. The sharing is now a declared object someone can find, rather than an ordinary-looking local that quietly became shared. | | Without the rule | With the rule | |---|---|---| | Capturing a reassignable local | allowed | rejected at compile time | | What a deferred read reports | depends on the capture model | the value at capture, always | | Where shared mutable state lives | possibly in a plain local | in a container the programmer declared | | Implementation | a cell when needed | a copy, always | ## What it costs Nothing is free, and the price shows up in three places: 1. **Ceremony for real sharing.** A running total accumulated by a closure across calls needs a declared one-slot holder and a field write instead of a plain assignment. The code says more than the idea does. 2. **Honest programs rejected.** The check is syntactic — is there an assignment to this name anywhere in its scope? — so code that assigns before any closure exists, or on a path that provably never runs alongside the capture, can still be refused. The rule trades a little expressiveness for a guarantee that needs no analysis. 3. **A false sense of safety.** Because the variable cannot be rebound, people describe the captured thing as frozen. It is not, and the record behind the handle will prove it. ## The escape hatch, and why it is the point Every language with this rule leaves one way out: put a container in the variable, capture the container, and write to its slot. Notice that this is the **same object the compiler would have synthesised** in a language without the rule. The difference is purely who writes it down. In one design the box is invisible and every captured local might be shared; in the other the box is in the source, named, and reviewable. That is the whole argument, and it is a design argument rather than a technical one: the two designs can express the same programs, and they disagree about whether shared mutable state should be something you can create by accident. Stating it that way — as a trade of expressiveness for legibility, not as one language being stricter than another — is what an interviewer is listening for.

  • Does the rule stop a closure from changing state shared with its creator?
    No. It stops the captured name from being reassigned. The programmer can still place a container in that variable and write through it, and both sides will see the writes. What changes is that the sharing is declared in the source instead of being created accidentally by an ordinary assignment.
  • Why would such a language compile the capture to a copy rather than to a shared location?
    Because with no possible later write the two are indistinguishable, and copying costs less: no allocation, no indirection, and no object whose lifetime has to be managed. The restriction turns what would be a conditional optimisation into the only case.

saying these in an interview costs you the question

  • The rule makes the captured data immutable.
  • It exists only to save an allocation.
  • Without such a rule a closure cannot capture locals at all.
  • A language with the rule cannot express a running total in a closure.