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.
answer
- one is a copied number, one re-reads
- object literal evaluates its values once
- only functions close over bindings
- same call, same environment
- getter turns a read into a call
basics
~20 sThe 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 sAssigning `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 linesfunction 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 constructiongo deeper
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.
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.
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.
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