skip to content

A factory returns `{ inc, value, get }` where `value` is set to the local `count` and `get` returns `count`. After calling `inc()` twice, `get()` returns 2 but `value` is still 0. Explain why, and how you would fix it.

level: middleimportance: should knowfreq 48%

answer

  1. one is a copied number, one re-reads
  2. object literal evaluates its values once
  3. only functions close over bindings
  4. same call, same environment
  5. getter turns a read into a call

basics

~20 s

The property value was assigned the number that count held while the object literal was being built, so it is a one-time copy. get closes over the count binding and re-reads it on every call, which is why only it stays current.

solid answer

~40 s

Assigning `value: count` in the object literal evaluates `count` **once**, at the moment the object is constructed, and stores that number on the property. Numbers are values, not references to a binding, so nothing links the property back to `count` afterwards. `get`, by contrast, is a closure: it stores a reference to the environment and resolves `count` each time it runs, so it observes every later increment. `inc` and `get` were created in the same invocation and therefore share one environment and one `count`. The fix is to make the read go through a function: either keep `get()`, or expose an accessor — `get value() { return count; }` — so the property lookup runs a closure instead of returning a stale snapshot.

code

javascript · 13 lines
javascript
function makeCounter() {
  let count = 0;
  return {
    inc() { count++; },
    value: count,
    get() { return count; }
  };
}
const c = makeCounter();
c.inc();
c.inc();
console.log(c.get()); // 2
console.log(c.value); // 0 — copied once at construction

go deeper

for a junior

Recognise that value: count stores a number once, while a function that returns count reads it fresh every time, and prefer the function when the state can change.

for a middle

Explain that only functions capture bindings, that an object literal evaluates its property expressions once, and that the two closures share one environment because one call created both.

for a senior

Spot this shape in review — a snapshot field sitting next to a live accessor — and choose deliberately between a getter, an explicit read function, and simply not exposing the state at all.

for a principal

Own the interface contract: decide whether consumers get a live view or an immutable snapshot of internal state, document it, and keep the API from offering both shapes for the same data.

## The shape of the bug ``` function makeCounter() { let count = 0; return { inc() { count++; }, value: count, // evaluated now, once get() { return count; } // evaluated on every call }; } const c = makeCounter(); c.inc(); c.inc(); c.get(); // 2 c.value; // 0 ``` Both `value` and `get` look like they expose the same state. Only one of them actually does. ## Why value is frozen An object literal is an expression. When it is evaluated, each property value expression is evaluated exactly once, in order. `count` at that instant is `0`, so the property is initialised to the number `0`. From then on, the property and the binding have no relationship whatsoever: JavaScript has no way to store "a reference to the variable `count`" in a property slot. Later increments change the binding; they cannot reach into the object. This is worth stating precisely, because it is the boundary of the closure rule. Closures capture bindings — but only *functions* close over anything. A plain property, an array element, a captured argument value: all of those hold values, and a value copied out of a binding stops tracking it. ## Why get() is live `get` is a function created inside the invocation of `makeCounter`, so it holds a reference to that invocation's environment. Its body contains the free identifier `count`, which is resolved by walking that environment chain when the call happens. That resolution finds the same binding `inc` mutates, so the two are always in agreement. ## Sibling closures share one environment `inc` and `get` were created during the same call, and both point at the same environment record. There is exactly one `count`. That is what makes the pair useful: a mutator and a reader over shared private state, with no field visible to the outside world. Call `makeCounter()` a second time and you get a second environment with its own `count`; the two objects are fully independent. So the sharing is precisely scoped to "closures born in the same call" — which is the mental rule worth carrying away. ## Fixes The simplest fix is to keep the reader as a function and delete the misleading data property. If you want property-style ergonomics, use an accessor so that the read *is* a call: ``` function makeCounter() { let count = 0; return { inc() { count++; }, get value() { return count; } }; } ``` Now `c.value` invokes a getter function, which closes over `count` and re-reads it. The syntax reads like a field and behaves like a closure. If you also want writes to be validated, pair it with `set value(v) { ... }`. A third option is to move the state onto the object itself — `this.count` — but then it is public and any caller can assign to it, which discards the privacy the closure was giving you. ## Diagnosing it in the wild The symptom is always the same: one accessor is current and another, apparently equivalent one is stuck at its initial value. In review, the tell is a property initialised from a mutable local in an object literal, or a value destructured out of state at construction time. Ask of every exposed field: *is this a value that was copied once, or a call that re-reads?* ## How to answer out loud Say that the object literal evaluated `count` once and stored a number, while `get` re-resolves the binding on each call; note that `inc` and `get` share one environment because they were created in the same invocation; then offer the accessor fix.

  • Would the property track the variable if count held an object instead of a number?
    Not the binding — but the appearance changes. The property would hold a reference to the same object, so mutations of that object are visible through both paths. Reassigning `count` to a different object would still leave the property pointing at the old one. The property tracks the value it was given, never the binding.
  • Do inc and get share state because they are on the same object?
    No — they share state because they were created during the same invocation of the factory and therefore reference the same environment record. Being properties of one object is incidental; two closures assigned to unrelated variables in the same call share just as much.
  • What does the accessor version cost compared with a plain field?
    A property read becomes a function call, so it is not a plain slot load and cannot be destructured into a live view — `const { value } = c` copies the number once again. It also makes the object non-serialisable in a naive sense: `JSON.stringify` will invoke the getter and store the current number, flattening the liveness.

saying these in an interview costs you the question

  • Says value is stale because objects are copied by reference
  • Claims inc and get have separate copies of count
  • Thinks the property updates automatically when count changes
  • Says only the object literal ran late, so ordering is the cause
  • Believes adding const or Object.freeze would fix the staleness

context