Dependency Injection Lifetimes (.NET)

Singleton, scoped and transient — how long a service instance lives, and why it matters.

Intermediate6 min read3 interview questions

Dependency injection (DI) containers create objects for you and hand them to the classes that need them. The lifetime you register decides how often a new instance is created.

Choosing the wrong lifetime is a classic source of production bugs: data leaking between requests, exhausted connections, or DbContext being used from two threads at once.

Diagram

HTTP request 1HTTP request 2Singleton #1one instance for the whole appScoped #1per requestScoped #2per requestTransient #1Transient #2Transient #3Transient #4
One singleton for everyone, one scoped instance per request, a new transient for every injection.

Key concepts

Singleton
Created once and shared for the app’s lifetime. Must be thread-safe. Good for configuration, HttpClient factories, caches.
Scoped
One instance per scope — in ASP.NET Core, per HTTP request. The default for DbContext and unit-of-work services.
Transient
A new instance every time it is requested. Good for lightweight, stateless helpers.
Captive dependency
A longer-lived service holding a shorter-lived one, e.g. a singleton that captures a scoped DbContext.

Example

Registering services in ASP.NET Core
builder.Services.AddSingleton<IClock, SystemClock>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddTransient<IPriceFormatter, PriceFormatter>();

// Enable scope validation so captive dependencies fail fast in development
builder.Host.UseDefaultServiceProvider(o => o.ValidateScopes = true);

Trade-offs

Benefits

  • Clear ownership of object lifetimes
  • Easy to swap implementations in tests
  • Scoped services give per-request consistency

Costs

  • Wrong lifetimes cause subtle concurrency and memory bugs
  • Large object graphs can hide expensive constructors

Use it when

  • Singleton for stateless or thread-safe shared services
  • Scoped for anything tied to a request or a database transaction
  • Transient for cheap, stateless objects

Common mistakes

  • Injecting a scoped service into a singleton (captive dependency)
  • Storing per-user data in a singleton — it leaks between users
  • Resolving scoped services in a background worker without creating a scope

Interview questions

Q1Why is DbContext registered as scoped?
It tracks changes for one unit of work and is not thread-safe. One instance per request keeps the change tracker consistent and avoids sharing it across threads.
Q2What happens if a singleton depends on a scoped service?
The scoped instance is captured for the life of the app, so it is shared across requests and threads. ASP.NET Core throws at startup when scope validation is on.
Q3How do you use a scoped service inside a hosted background service?
Inject IServiceScopeFactory, create a scope for each unit of work, and resolve the service from that scope.