skip to content

A queued audit entry captures a status variable that its caller later reassigns — what decides which value the entry reports?

level: juniorimportance: must knowfreq 66%

answer

  1. two possible answers, not one
  2. copy, or a link
  3. does a later assignment travel inward?
  4. value snapshot versus the binding itself
  5. capture a fresh, never-reassigned name

basics

~20 s

Capture mode decides. A by-value capture copied the status when the entry was created and reports the old value; a capture of the binding itself reads the variable at call time and reports the new one.

solid answer

~40 s

A closure has to record something about every free variable it uses, and there are only two candidates: the variable's `value` at the moment of capture, or the `binding` — the storage location the name refers to. Copy the value and the entry is a snapshot, immune to anything the caller does afterwards. Hold the binding and the entry reads the live variable when it finally runs, so a later assignment shows up. Languages differ on which they do, and a few let you pick per capture. The difference is only observable when three things line up: the entry is created, the variable is reassigned, and only then is the entry called. If you need the value as it was, copy it into a fresh name that nothing reassigns and capture that instead.

code

pseudocode · 10 lines
pseudocode
status = "pending"

auditEntry = function() {
    report(status)          // status is free here: it comes from outside
}

status = "approved"         // the caller reassigns after capture

auditEntry()                // copy of the value  -> "pending"
                            // the binding itself  -> "approved"

go deeper

for a junior

Know that a closure records either a copy of the value or the variable itself, and that the two only differ when someone reassigns the variable before the closure runs.

for a middle

Explain what is stored in each model, what happens when the closure assigns back to the captured name, and how to force a snapshot by capturing a name that is never reassigned.

for a senior

Recognise the symptom in real code: deferred work reporting the newest value, a bug that disappears whenever someone calls the function inline while investigating.

for a principal

Decide how the codebase handles it as a rule — whether deferred work is allowed to capture live variables at all, or must take finished values as arguments so the question cannot arise.

## What a closure actually holds A **closure** is a function value bundled with the variables it used from the scope where it was written. A variable is **free** in the function body when it is neither a parameter nor a local of that function — the body mentions `status`, but `status` belongs to the enclosing scope. Creating the closure therefore has to record *something* about `status`, because the body may be evaluated much later, possibly after the enclosing call has already returned. There are exactly two things it can record, and the whole subject reduces to which one a language picks: - **The value (capture by value).** The contents of the variable are copied, at the moment of capture, into storage that belongs to the closure. From then on there are two independent locations that happen to have started equal. - **The binding (capture by reference to the variable).** The closure records the variable itself — the location the name denotes. Every read in the body goes to that location, at the time the body runs. Neither is "the" correct answer. A language picks one, and some let the programmer choose per capture with an explicit copy step. ## Why the difference only shows up under reassignment Capture mode is invisible until three events happen **in this order**: 1. the closure is created, capturing the variable; 2. the enclosing scope **assigns a new value to that variable**; 3. the closure is **called after** that assignment. Drop any one of the three and both models print the same thing. That is why the bug class hides in code that is deferred — queued, scheduled, registered as a handler — and disappears the moment someone calls the function inline to "check". | | Capture by value | Capture of the binding | |---|---|---| | What the closure stores | a copy of the contents | the variable itself | | Caller reassigns after capture | invisible inside | visible on the next call | | Closure assigns to the name | changes its own copy only | the caller sees it too | | Storage needed | one extra slot per capture | one location shared by both | | Typical language restriction | none needed | often none, but some languages forbid capturing a reassignable variable at all | ## Getting the behaviour you want Whatever the language does by default, you can always force the snapshot, and it takes one line: 1. **Introduce a fresh name** immediately before the closure is created and assign the current value to it. 2. **Never reassign that name.** A variable that is written once behaves identically under both models, which is precisely why the trick works everywhere. 3. **Capture the fresh name** in the closure body instead of the original. Forcing the other direction — making a closure see later changes in a language that copies — is not symmetric. You cannot un-copy a value; you have to give the closure something whose *contents* can change, which is a different mechanism with different consequences. ## What capture mode does not decide Three neighbouring questions get folded into this one and should not be: - **Which variable the name refers to.** That is settled by where the function was written, before capture even happens; it is a question about scope, not about capture. - **Whether a change made to the object a captured variable refers to is visible.** It is, under both models: copying a variable copies its contents, and if those contents are a handle to something mutable, both sides end up looking at the same thing. Capture governs *reassignment of the name*, not writes to what the name points at. - **How long the captured data is kept alive.** A capture keeps something reachable; that cost is real but separate. ## Why interviewers ask it The question is cheap to state and exposes the model instantly. A candidate who answers "it prints the old value" flatly, with no mention of what the closure recorded, is reciting one language's behaviour. A candidate who says "depends whether it copied the status or kept the variable — and here is the three-line test that tells you which" has the mechanism, and will get the same answer right in a language they have never used. The follow-up that separates the field further is whether the closure can assign *back* to the captured name: under a copy it writes its own slot and the caller sees nothing, which is exactly why several languages refuse to allow it.

  • Under a by-value capture, what happens when the closure assigns to the captured name?
    It writes its own copy. The enclosing variable is untouched, so the two sides silently drift apart, and a caller reading the original afterwards sees nothing. Because that is more confusing than useful, several languages remove the possibility: they either reject the assignment or refuse the capture.
  • How would you find out, in three lines, which model a language uses?
    Assign a value to a local, create a closure that reads it, reassign the local, then call the closure. Printing the original value means the contents were copied at capture; printing the new one means the closure holds the variable. The reassignment has to sit between the capture and the call.
  • If the entry is called before the reassignment, do the two models differ?
    No. Both read the same contents, because nothing has changed yet. The models can only be told apart by a call that happens after an assignment, which is why the difference surfaces in deferred work and vanishes when someone invokes the closure immediately to check.

Capturing the value is photographing the noticeboard; capturing the binding is keeping a key to the room it hangs in. When someone pins up a new notice, only the key-holder ever sees it.

saying these in an interview costs you the question

  • A closure always copies everything it captures.
  • The closure remembers whatever it printed the first time.
  • Reassigning the variable replaces the closure with a new one.
  • Capture works the same way in every language.
  • If the value changed, the closure must have captured the wrong variable.