In a framework's component container, how can a singleton reach the per-request component of the request in flight?
answer
- resolve late, never capture
- parameter, factory, or proxy
- proxy forwards to the active scope
- no active scope must fail loudly
basics
~20 sResolve late instead of capturing: the handler passes the value in as a method parameter, or the singleton holds a factory it calls per use, or a scope proxy that forwards every call to the active request's instance.
solid answer
~50 sThree mechanisms, in the order I would reach for them. First, **pass it in**: the handler is already inside the request, so it hands the singleton what it needs as a method parameter — no scope awareness, trivially testable, and the dependency is visible in the signature. Second, inject a **factory or accessor**: the singleton holds something that resolves the current scope's instance on each call, which keeps resolution explicit while allowing use deep inside the singleton's own logic. Third, inject a **scope proxy**: a stand-in object of the same shape that holds no instance itself and forwards every call to whatever the active scope holds, so callers are unchanged. All three replace *capture at construction* with *resolve at call*. Whichever you pick, a call made when no request scope is active must fail loudly rather than invent or reuse an instance.
go deeper
Know that a long-lived component cannot simply hold a request-scoped one, and that the simplest way round it is to let the handler pass the value in as an argument.
Explain what a proxy or factory actually does — it resolves on each call instead of holding an instance — and why that is the difference between correct and captured.
Weigh the three mechanisms out loud: signature churn versus an explicit accessor versus an invisible proxy, and state what must happen when no scope is active.
Decide which seam a codebase standardises on and why, knowing that an invisible proxy is cheap to adopt and expensive to reason about once thousands of call sites depend on it.
## Why a plain reference cannot work A component with the process-wide lifetime is constructed once. Whatever it is handed at that moment it keeps forever. A per-request component, by definition, is only correct for the one request it was created in. So the two cannot be connected by an ordinary field: there is no moment at construction time at which "the current request's instance" is a meaningful value for all future requests. Everything that works therefore shares one property: **the identity of the request-scoped object is decided when the call happens, not when the holder is built.** ## Mechanism 1 — pass it in as a parameter The handler runs inside the request scope, so it can resolve the request-scoped component itself and pass what the collaborator needs as a method argument. - The dependency is visible in the signature, so nobody has to know the container exists to read the code. - The collaborator becomes a plain object in tests: no scope to open, no container to configure. - The cost is signature churn — if the value is needed five layers down, five signatures grow. ## Mechanism 2 — inject a factory or accessor The singleton holds a small object whose single job is to return the current scope's instance on demand, and it calls that on every use. - Resolution is explicit and visible at the call site, which makes the lifetime relationship obvious in review. - It works at any depth without threading a parameter through intermediate layers. - It couples the component to a container-shaped abstraction, so tests must supply a stand-in accessor. - It is the form most lifetime validators accept as the sanctioned escape hatch from the longer-holds-shorter rule. ## Mechanism 3 — inject a scope proxy The container injects an object that implements the same interface as the request-scoped component but holds no state of its own. Each call is forwarded to whatever instance the active scope currently holds. - Callers are untouched: the singleton's constructor still declares the dependency it wants, and the wiring looks ordinary. - That invisibility is also the drawback. Nothing at the call site says the resolution is happening per call, and a reader who assumes an ordinary reference will reason about the code incorrectly. - It normally requires the dependency to be an interface or otherwise substitutable, and it adds an indirection on every call. ## Comparing them | Mechanism | Dependency visible at the call site | Works deep in the call stack | Test setup | Main risk | |---|---|---|---|---| | Method parameter | Yes, in the signature | Only by threading it through | None | Signature churn across layers | | Factory or accessor | Yes, at each use | Yes | Stand-in accessor | An extra abstraction to understand | | Scope proxy | No | Yes | Active scope or stand-in | Hidden per-call resolution and hidden failure modes | ## When no scope is active Any late-resolving mechanism has a case with no answer: a call made when no request scope is open. The honest behaviour is an explicit failure that names the problem. The tempting alternatives are all worse: - Creating a detached instance produces work that belongs to no request and silently succeeds. - Returning empty or default values turns a wiring mistake into wrong output somewhere further downstream. - Reusing the most recently closed scope's instance reintroduces exactly the cross-request leakage that scoping exists to prevent. ## What not to do - **Do not stash the request-scoped object in a process-wide mutable field** for the singleton to read. Concurrent requests then overwrite each other, and the failure is timing-dependent and rare enough to survive testing for months. - **Do not inject the container itself** and resolve from it wherever convenient. The dependency disappears from the constructor, every test needs a configured container, and the component can now reach anything at all — the object graph stops being reviewable. - **Do not promote the request-scoped component to the longer lifetime** to make the error go away. That deletes the scoping rather than satisfying it, and the state that made it request-scoped is now shared by every caller. ## Choosing in practice Prefer passing the value in whenever the need is shallow — it is the only option that leaves the collaborator ignorant of scopes entirely. Use an accessor when the need is deep and you want the per-call resolution to be visible. Reserve the proxy for cases where you cannot change the consuming code, and document it, because the whole point of a proxy is that the code does not show what it is doing.
- What does a scope proxy cost compared with an explicit factory?Readability and a small per-call indirection. The proxy makes the wiring look like an ordinary reference, so a reader cannot see that resolution happens per call, and the failure when no scope is active appears from code that never mentions scopes. A factory states the relationship at every use, which is why validators and reviewers prefer it.
- Why is injecting the container into a singleton a poor substitute for these mechanisms?It hides the dependency: the constructor no longer declares what the component actually needs, so the graph is no longer reviewable and every test must build a configured container. It also grants unlimited reach — the component can resolve anything — which turns a narrow lifetime problem into an open-ended coupling problem.
- How would you test a singleton that reaches into request scope?Supply a stand-in for the late-resolution seam rather than opening a real scope: a fake accessor or factory that returns a prepared object. If the value arrives as a method parameter there is nothing to stand in at all. Needing a live request scope to unit-test a component is a sign the seam is in the wrong place.
saying these in an interview costs you the question
- Suggests storing the request-scoped object in a shared static field for the singleton to read
- Thinks a scope proxy holds the request's instance rather than resolving it per call
- Says injecting the container everywhere is the clean general solution
- Believes a proxy makes the singleton usable when no request scope is active
- Assumes passing the value as a method parameter is inferior design to injecting it