skip to content

What does the JavaScript function `function counter() { let n = 0; try { return n; } finally { n = 100; } }` return, and would the answer change if it returned an object that the finally block then mutated?

level: middleimportance: must knowfreq 58%

answer

  1. the value leaves before the block does
  2. completion value captured at return
  3. primitive copied, object still shared
  4. reassigning the variable changes nothing
  5. mutating the returned object is visible

basics

~20 s

It returns 0. The return expression is evaluated and its value parked before finally runs, so reassigning the variable afterwards changes nothing. Returning an object is different: the caller sees the mutation, because both hold the same reference.

solid answer

~40 s

`counter()` returns `0`. When `return n` executes, the engine evaluates `n`, stores that value as the pending result of the function, and only then runs the `finally` block. Assigning `n = 100` inside `finally` rebinds the local variable, but the value that was already captured is untouched, so `0` is what the caller gets. Change the returned thing to an object and the picture flips: `try { return box; } finally { box.n = 100; }` still captures the same reference, but the object it points at is shared, so the caller reads `box.n === 100`. The rule is that `finally` cannot replace the parked completion value by ordinary assignment - it can only reach through it, and only if the value is a reference to something mutable.

code

javascript · 20 lines
javascript
function primitiveCase() {
  let n = 0;
  try {
    return n;
  } finally {
    n = 100;
  }
}

function objectCase() {
  const box = { n: 0 };
  try {
    return box;
  } finally {
    box.n = 100;
  }
}

console.log(primitiveCase());   // 0
console.log(objectCase().n);    // 100

go deeper

for a junior

Recall the headline result: the value is taken at the moment the return statement runs, so a later assignment in finally cannot change it. Say 0, and say why in one sentence.

for a middle

Explain the completion-value mechanism step by step, then contrast the object case and show you know that mutation reaches through a captured reference while reassignment does not.

for a senior

Connect it to a real defect: cleanup that resets or recycles an object the function just handed out. Describe how you would spot it in review and how you would restructure ownership so it cannot happen.

for a principal

Own the wider convention - what a returned value means about ownership and mutability in your codebase - so this class of aliasing surprise is designed out rather than caught one puzzle at a time.

## Why this puzzle is asked It is a two-line program with a counter-intuitive answer, and getting it right requires knowing something real about how the engine models `return` - not a memorised trivia fact. It also quietly tests whether the candidate distinguishes rebinding a variable from mutating an object, which is the same distinction behind half the aliasing bugs in JavaScript. ## Completion values The specification describes the result of running any statement as a *completion*: a kind (normal, return, throw, break, continue) plus a value. When `return n` runs inside a `try` block, the engine evaluates the expression `n` to the number `0` and produces a return completion carrying `0`. That completion is then held while the `finally` block runs. So the sequence is precisely: 1. evaluate the return expression -> `0`; 2. record "this statement wants to return `0`"; 3. run the `finally` block; 4. if `finally` finishes normally, resume the recorded completion and return `0`. Step 3 has no access to the recorded value. `n = 100` writes to the variable binding `n`; nobody re-reads `n` afterwards, so the write is simply irrelevant to the result. ```js function counter() { let n = 0; try { return n; } finally { n = 100; } } counter(); // 0 ``` ## The object case Now return a reference instead of a primitive: ```js function boxed() { const box = { n: 0 }; try { return box; } finally { box.n = 100; } } boxed().n; // 100 ``` Nothing about the mechanism changed. The engine still evaluates the return expression and parks the value - but the parked value is a *reference* to an object, and `finally` mutates the object that reference points at. The caller then follows the same reference and sees the mutation. There is no contradiction between the two examples: in both, the parked value is untouchable; in the second, the parked value is a pointer to something that is not. This is exactly the `x = other` versus `x.prop = other` distinction that shows up everywhere in JavaScript, just observed through an unusual window. ## The variant people expect to work A frequent instinct is to "fix up" a result in `finally`: ```js function attempt() { let result = 'pending'; try { result = doWork(); return result; } finally { result = 'cleaned'; // has no effect on what is returned } } ``` If you genuinely need `finally` to influence the outcome, assignment is the wrong tool - the only way a `finally` block changes the result of a statement is by completing abruptly itself (a `return`, `throw`, `break` or `continue` inside the block), which is a heavy hammer with its own hazards. Restructure instead: compute the value in the try block, or return after the try statement. ## Ordering of side effects The same rule tells you when observable side effects happen: ```js function trace() { try { console.log('A'); return sideEffect(); // sideEffect() runs here } finally { console.log('B'); } } // A, then sideEffect(), then B ``` Anything in the return expression - a function call, a template literal, a getter - has already run by the time `finally` starts. So a `finally` block that resets state can still see the effects of the expression it is "cleaning up after", and a getter invoked in the return expression reads the state *before* the reset. ## Practical takeaways - `finally` is for cleanup, not for shaping the return value; treat the returned value as already settled by the time the block runs. - If a cleanup block resets a variable that was also returned, the reset is invisible to the caller for primitives and visible for objects - so returning a mutable object out of a scope whose cleanup touches it is a genuine trap. Return a copy, or move the mutation before the `return`. - Being able to explain this with the words "the value is captured, then the block runs" is what separates an answer that generalises from a memorised `0`.

  • If the return expression is a function call, has that call already happened by the time finally runs?
    Yes. The return expression is fully evaluated first - the call executes, its side effects land, and the result is parked as the pending return value. Only then does the finally block start. So cleanup in finally always runs after the work in the return expression, never before it.
  • How would you make a finally block genuinely change what the function returns?
    Only an abrupt completion inside the block does that - a `return`, `throw`, `break` or `continue` written in the finally body replaces the parked completion. It is deliberately awkward because it also discards pending errors. The clean fix is restructuring: compute the value in the try block, or return after the whole try statement.
  • Is there a real bug hiding behind the object version of this puzzle?
    Yes. If a cleanup block resets or recycles an object that was also returned, the caller silently receives the reset state. Pooled buffers and reused config objects fail this way. Return a copy, or perform the mutation before the return, so ownership of the returned object is unambiguous.

saying these in an interview costs you the question

  • Answers 100, assuming finally runs before the return is evaluated
  • Says the return value is re-read after finally completes
  • Claims objects and primitives behave identically here
  • Thinks assigning in finally can override the returned value
  • Explains it as a JavaScript quirk with no mechanism

context