In a web framework's component container, how do singleton, per-request and transient lifetimes differ?
answer
- how long one instance is reused
- process, request, or every resolution
- shared instance needs thread safety
- shortest lifetime only for per-request state
basics
~20 sA lifetime decides how long the container reuses one instance. A singleton lives as long as the process and serves every request; one per-request instance serves a single request; a transient is built fresh at every resolution.
solid answer
~50 sA container hands out instances according to a declared lifetime. A **singleton** is built once per process and shared by every request, so it must be stateless or internally thread-safe. A **per-request** instance is built once per inbound request and shared by everything resolved while that request is handled, which is what makes it a safe carrier for that request's own state, and it is released when the request ends. A **transient** is built at every resolution, so two collaborators asking for the same type get two objects and nothing is shared. Most frameworks default to the process-wide lifetime for stateless collaborators such as data access, outbound clients and handlers, because building that graph once avoids rebuilding it per request. Reach for a shorter lifetime only when the object genuinely holds one request's state or owns a resource that must be released with it.
go deeper
Be able to name the three lifetimes and say what each means in one sentence. The part interviewers listen for is that a process-wide instance is shared by requests running at the same time.
Explain the mechanism: the container keeps an instance map per scope, so a lookup hits the process-wide map, the current request's map, or builds new every time. Say what each choice costs.
Show that you pick a lifetime from the state an object holds and the resources it owns, and that you treat a mutable field on a shared component as a production defect waiting for concurrency.
Frame lifetime as policy: what the codebase defaults to, how the graph is validated at startup, and what a deep per-request graph costs in allocation at peak traffic.
## What a lifetime actually is The **container** is the part of a server-side web framework that builds the object graph behind the handlers: it knows how to construct each component and how to supply that component's own dependencies. Every registration answers two separate questions — *how do I build this?* and *how long do I reuse what I built?* The second answer is the component's **lifetime** (also called its **scope**). Mechanically, a container keeps an **instance map per scope**. When something asks for a component, the container looks in the map belonging to that component's scope. A hit returns the existing instance; a miss builds one, stores it and returns it. Different lifetimes are simply different maps with different opening and closing rules. ## The three lifetimes most frameworks offer | Lifetime | One instance per | Shared by | Released when | Typical use | |---|---|---|---|---| | Singleton | process | every request and every collaborator | shutdown | stateless collaborators, outbound clients, pools, parsed configuration | | Per-request | inbound request | everything resolved while that request is handled | the request ends | request identity, a unit of work, per-request resources | | Transient | resolution | nothing — no sharing at all | with the scope that created it, if the container tracks it | cheap stateful helpers, builders | Frameworks use different words for these — application-wide, container-wide or root for the first; request, scoped or per-call for the second; transient, prototype or per-resolution for the third — but the three behaviours are recognisably the same everywhere, and interviewers ask about the behaviours. ## Singleton: one instance for the process - It is created once, either eagerly during startup or lazily on first use, and then kept for the life of the process. - Concurrent requests genuinely touch the *same object*, so any mutable field is shared mutable state. A singleton is safe when it holds configuration, pooled resources and other collaborators, and unsafe the moment it holds one caller's data. - It is the right home for anything expensive to build: an outbound client with its connection pool, a compiled route table, parsed settings. - A singleton may only depend on components that live **at least as long as it does**. Holding a shorter-lived one freezes that instance for the life of the process, which is the classic lifetime bug. ## Per-request: one instance for one request - The scope opens when the framework accepts an inbound request and closes when that request is finished. Everything resolved inside that window receives the **same** instance. - That shared identity is the point: two collaborators that both need "the unit of work for this request" get the same one without passing it through every signature. - Release is deterministic — when the scope closes the container runs the release hooks of what it created there, so a borrowed connection or buffer goes back. - The cost is construction per request. A deep per-request graph is rebuilt on every single call, which shows up in allocation and tail latency at high request rates. ## Transient: one instance per resolution - There is no reuse and no identity guarantee. Ask twice, get two objects, even inside one request. - It is the cheapest lifetime to reason about, because nothing is shared and nothing can leak between callers. - It is *not* automatically tied to a request. A transient resolved during a request is released with that request only if the container tracks disposables in the scope that created them; some containers track them in the longest-lived scope instead, which quietly accumulates objects that were meant to be short-lived. ## How to choose 1. **Ask what state the object holds.** No per-caller state means the process-wide lifetime, which is the default in most frameworks for good reason. 2. **Ask what it owns.** If it holds a resource whose release must happen when the request finishes, it belongs in the request scope so the container can release it there. 3. **Otherwise prefer the longest lifetime that is correct.** Shorter lifetimes cost allocation and restrict who may hold the component. ## What each choice costs you - Singleton: permanent thread-safety discipline, and hidden shared state is a production bug rather than a test failure. - Per-request: a rebuilt object graph per request, plus the constraint that longer-lived components cannot hold these instances directly. - Transient: no deduplication, so a component resolved five times in one request does five constructions and any state it accumulates is invisible to the other four. The rule that ties all three together is one sentence worth memorising: **a component may hold only components whose lifetime is at least as long as its own.** Everything else about lifetimes follows from it.
- Why do most frameworks make the process-wide lifetime the default rather than per-request?Because most collaborators are stateless: a router, a data-access component or an outbound client holds configuration and pooled resources, not caller data. Building that graph once avoids wiring it on every request, which matters at high request rates. Per-request is the exception you opt into when an object genuinely carries one request's state or owns a resource released with it.
- If both are short-lived, what is the practical difference between transient and per-request?Per-request gives a shared identity: everything resolved during that request receives the same instance, so collaborators see each other's state and the container releases it once at scope close. Transient guarantees the opposite — two resolutions produce two objects — and it is released with the surrounding scope only if the container tracks disposables there.
saying these in an interview costs you the question
- Thinks a container singleton is the same thing as a hand-written global static instance
- Says transient is always safest, so everything should be registered transient
- Believes the container builds one singleton instance per thread or per core
- Assumes a transient object is always released when the request that built it ends
- Keeps mutable per-request state in a shared component and expects the container to isolate it