A caching wrapper speeds up calls from outside an object but never fires for a call the object makes on itself, why?
answer
- interception is a boundary technique
- only calls that arrive are seen
- which reference carries the inner call
- self-reference points at the target
- no stand-in in the path, no behaviour
basics
~20 sInterception only runs where a call crosses from one object into another. A wrapper sees the calls made through the reference it was handed; a call the object makes on itself travels through its own self-reference and never reaches the wrapper.
solid answer
~40 sA wrapper adds the behaviour on **its own copy** of the member and then forwards to the target, leaving the target's code untouched. Outside callers are handed the wrapper, so their calls land on it first. But when the object invokes one of its own members, the receiver is its internal self-reference, which points at the target; the wrapper is not on that path and never had a chance to run. The bug is silent, because the call still succeeds and only the caching, retry or audit step is missing. It is also path-dependent, so a test that drives the member from outside passes while the production path through the self-call does not.
code
pseudocode · 14 linesobject Report:
method summary():
rows = expensive() // receiver is the object itself
return format(rows)
method expensive():
return slowQuery()
object CachingStandIn(target):
method summary(): return cacheOrCall("summary", target.summary)
method expensive(): return cacheOrCall("expensive", target.expensive)
handedOut = CachingStandIn(Report())
handedOut.expensive() // arrives at the stand-in, cached
handedOut.summary() // summary cached; the expensive() inside it is notgo deeper
Recall that a wrapper is a separate object placed in front of the real one, and that it can only act on calls that are actually sent to it.
Explain which reference carries an internal call, why the wrapper is not on that path, and why the symptom is a silent no-op rather than an error.
Diagnose it in a running system: show the behaviour applies from outside, trace the path of the failing call, and name the reference it travelled through.
Judge whether a mechanism whose failure mode is silence is acceptable for the concern involved, and what evidence you would demand that it actually applied.
## The parts: a target, a stand-in, and the reference in between A **stand-in** (a wrapper, an interceptor) is an object that satisfies the same contract as a **target** object and holds a reference to it. Every member on the stand-in does the same three things: run some added behaviour, forward the call to the target, and run more added behaviour on the way out. Caching, retrying, timing, auditing and permission checks are all built this way, and the appeal is that the target's own source never changes. That appeal is also the source of the blind spot. The added behaviour lives **on the stand-in's copy of the member**, not on the target's. So it runs exactly once per call that *arrives at the stand-in*, and not at all for a call that arrives anywhere else. ## Why the self-call never arrives When code outside the object asks for a collaborator, the wiring hands it the stand-in, so its calls land there first and the behaviour fires. Inside the target the situation is different. When one of the target's members calls another member of the same object, the receiver of that call is the object's **own reference to itself**, and that reference points at the target, because the target is the object the code is executing in. The target was never told that a stand-in exists, and nothing rewrote the call site. So the call passes straight from one of the target's members to another. Interception is a **boundary technique**: it can only act where a call crosses from one object to another, and between two members of the same object there is no boundary to stand on. | call | receiver | added behaviour runs? | |---|---|---| | outside caller through the handed-out reference | the stand-in | yes | | member to another member of the same object | the target itself | no | | outside caller holding a reference obtained before wrapping | the target | no | | member to a collaborator that is itself wrapped | that collaborator's stand-in | yes | The last row matters: an internal call is not doomed by being internal. It is doomed by having the target itself as its receiver. A call the target makes outward, to some other object that is wrapped, is intercepted normally. ## Three properties that make it a favourite interview trap - **It fails silently.** Nothing throws and no log line appears. A cache that is never consulted is indistinguishable from a cache that always misses, and an audit step that never ran leaves exactly the evidence you would expect from a call that never happened. - **It is invisible at the declaration.** The member carries whatever marker requests the behaviour, and that marker says nothing about who is allowed to call it. Reading the member tells you nothing about which of its callers are covered. - **It is path-dependent.** The same member behaves one way through the handed-out reference and another way through the self-reference, so coverage depends on the call graph rather than on the code you are looking at. ## The same rule wearing other faces 1. **A captured callback.** If the target hands one of its own members to something else to invoke later, what was captured is the target, so every later invocation enters the target directly. 2. **A reference that escaped.** Any reference to the raw target obtained before or around the wiring keeps working and keeps bypassing the stand-in. 3. **Construction-time work.** A wrapper is placed around an object that already exists, so whatever the target did while it was being built happened before there was anything to intercept it. All three are the same sentence: only calls whose receiver is the stand-in are intercepted. ## Reading the symptom The diagnosis runs in three steps. First, confirm the behaviour applies at all by exercising the member from outside; if it does not fire there either, the problem is the wiring, not the call path. Second, follow the path the failing call actually took and look for a member of the same object calling another. Third, ask which reference carried the failing call: the handed-out one, or the object's own. A stack trace helps here, because the frames belonging to the interception layers are present on a covered path and absent on a bypassed one. ## What this is not It is not a defect in how the stand-in was produced. Whatever technique built it, a call that does not leave the target cannot be seen. And it is not an ordering problem between several stacked handlers: with a self-call, no handler ran at all, so no rearrangement of them changes anything.
- Does the same blind spot appear when the target hands one of its own members to something else as a callback?Yes. What is captured is the target, not the stand-in, so every later invocation through that callback enters the target directly and the added behaviour is absent. It is the same rule on a different path: the receiver decides whether interception happens, and the receiver there is the raw object.
- Why does a test that calls the member directly fail to catch this?Because the test is an outside caller: it is handed the stand-in, so it takes the covered path. The failing path only exists when the object calls itself, so the test has to drive the outer member that contains the internal call, and assert on the effect of the added behaviour - a second call served from the cache, an audit record - rather than on the return value.
A receptionist who screens every call arriving at the front desk cannot screen the calls staff make to each other from inside the building.
saying these in an interview costs you the question
- Claims the wrapper also rewrites calls the target makes internally.
- Says the behaviour lives on the member, so every caller of it is covered.
- Expects a loud failure when a call bypasses the wrapper.
- Treats a passing test that calls from outside as proof the path is covered.
- Blames handler ordering when in fact no handler ran at all.