A JavaScript module is built like this: `const mod = (function () { function a() { return 'original a'; } function b() { return a(); } return { a, b }; })();`. A caller then does `mod.a = () => 'patched a';`. Why does `mod.b()` still return 'original a', and what does that tell you about what the returned object holds?
answer
- the literal copies values, not links
- identifiers resolve through scope, not properties
- internal calls bypass the public object
- patch works outside, not inside
- reassignment drifts in both directions
basics
~20 sInside the wrapper, b calls the local binding a, not the property mod.a. The returned object holds a copy of the function value at return time, so replacing the property rewires only outside callers; every internal call keeps going to the original.
solid answer
~50 sThe returned object literal copies the current *values* of `a` and `b` into two properties. After that, the property and the inner binding are two independent references to what was the same function. `b`'s body contains the identifier `a`, which resolves through the scope chain to the wrapper's local binding — it never goes through `mod`. So patching `mod.a` changes what external callers get and nothing else. This is the well-known drawback of the revealing module pattern: the public surface is not a set of hooks, so you cannot override behaviour from outside, and a caller who monkey-patches a method will see it apply inconsistently. The mirror-image case is just as surprising — if the module reassigns its own inner `a` later, `mod.a` keeps pointing at the old function while `b` starts using the new one. If you want a genuinely overridable seam, have the internals call through the object itself, or take the dependency as a parameter.
code
javascript · 20 linesconst mod = (function () {
function a() { return 'original a'; }
function b() { return a(); } // resolves `a` via the scope chain
return { a, b };
})();
mod.a = () => 'patched a';
console.log(mod.a()); // 'patched a'
console.log(mod.b()); // 'original a' <-- the patch never reaches here
// Dispatching through the object instead gives a real override seam:
const hooked = (function () {
return {
a() { return 'original a'; },
b() { return this.a(); },
};
})();
hooked.a = () => 'patched a';
console.log(hooked.b()); // 'patched a'go deeper
Know that returning { a, b } copies the current function values into properties, and that changing a property does not change the variable it was copied from.
Explain that an identifier inside the module resolves through the scope chain rather than through the returned object, and show the divergence in both directions.
Diagnose it from symptoms: a stubbed method that only takes effect on direct calls, and internal call sites the patch never reaches. Say when the snapshot is the behaviour you want.
Own the API-design call: decide deliberately whether the module publishes overridable seams or a closed surface, and set the team's rule that seams are declared, not monkey-patched.
## What the return statement actually does `return { a, b };` is shorthand for `return { a: a, b: b };`. Object literals evaluate their property values eagerly: at that instant the engine reads the current value of the binding `a`, which is the function object, and stores that reference in a new property. There is no ongoing link between the property `mod.a` and the variable `a`. They start out pointing at the same function object and can drift apart the moment either side is reassigned. ## Why `b` does not consult `mod` `b`'s body contains the identifier `a`. Identifier resolution has exactly one strategy: walk the scope chain outward from where the code was written until a binding with that name is found. `b` was written inside the wrapper, so the first `a` it finds is the wrapper's local one. The object `mod` plays no part in that lookup — `mod` is a value the wrapper never even names, since it is created by the caller's assignment after the call returns. The consequence is a two-world split: ```js mod.a(); // 'patched a' -- external callers see the patch mod.b(); // 'original a' -- internal calls do not ``` That inconsistency is what makes the bug expensive: the patch appears to work when you test it directly and silently fails in every code path that goes through another method. ## The mirror case The same decoupling bites from the inside out: ```js const mod2 = (function () { let a = () => 'v1'; const b = () => a(); function upgrade() { a = () => 'v2'; } // reassign the inner binding return { a, b, upgrade }; })(); mod2.upgrade(); mod2.b(); // 'v2' -- b closes over the live binding mod2.a(); // 'v1' -- the property still holds the old function value ``` So the returned object is a snapshot while the internal references are live. Whichever direction the reassignment happens in, the two views disagree. ## Contrast with the object-literal style Write the same module with the methods defined directly on the returned object and calling each other through `this`, and the patch takes effect everywhere: ```js const mod3 = (function () { return { a() { return 'original a'; }, b() { return this.a(); }, // dispatches through the object }; })(); mod3.a = () => 'patched a'; mod3.b(); // 'patched a' ``` That is a genuine tradeoff rather than a fix. Dispatching through `this` makes the method overridable — good if you want a seam, bad if the internal call was supposed to be an implementation detail that no caller can subvert. It also inherits every `this`-binding hazard: pull `mod3.b` out into a variable or pass it as a callback and the receiver is lost. ## Choosing deliberately Decide what you want the public surface to *be*. If it is a closed API — the module's behaviour is fixed and callers only consume it — the snapshot semantics are a feature. Nobody can monkey-patch a code path in a way that half-works, and the internals are free to be refactored because no caller can reach into them. If you genuinely need a seam — for tests, for a pluggable strategy, for an environment-specific implementation — do not rely on property patching. Take the collaborator as a parameter to the factory, or expose an explicit setter that reassigns the *inner* binding: ```js const mod4 = (function () { let formatter = (s) => s.trim(); return { setFormatter(fn) { formatter = fn; }, // rewires the internal binding render(s) { return formatter(s); }, }; })(); ``` Now the rewiring is part of the contract, is visible in the public surface, and applies to every internal call — no snapshot to drift out of sync. ## Diagnosing it in the wild The signature of this bug in a real codebase is a test that stubs a module method and passes, alongside production behaviour that never changed — or the reverse, a stub that appears to leak because some other method still runs the original and mutates shared state. When you see "I patched it and it only sometimes takes effect", check whether the caller is going through the property or through a closed-over binding. Grep the module for internal calls to its own exported names; each one is a call site the patch will not reach.
- If the module reassigns its own inner `a` after returning, what does `mod.a` do?Nothing — it keeps pointing at the function that was current when the object literal was evaluated. Meanwhile every internal call that names `a` picks up the new function, because those calls resolve the binding at call time. The property is a snapshot; the binding is live, so the two views diverge.
- How would you give the module a real override seam without exposing its internals?Make the rewiring explicit. Either accept the collaborator as a parameter when constructing the module, or publish a small setter that assigns the inner binding — `setFormatter(fn)` — so every internal call picks it up. Both keep the seam in the contract instead of relying on callers patching properties.
- What is the cost of writing `b() { return this.a(); }` so the patch does take effect?You have made every internal call an overridable dispatch, so callers can now change behaviour you may have intended to be fixed. You also inherit receiver problems: detach the method into a variable or a callback and `this` is no longer the module, so the internal call fails.
saying these in an interview costs you the question
- Says the returned object keeps a live link to the inner functions
- Claims `b` looks up `a` on the module object
- Thinks reassigning `mod.a` also reassigns the inner binding
- Believes hoisting makes the two references stay in sync
- Says patching works and blames the test framework when it does not