What are service lifetimes in ASP.NET Core?
Quick Answer
A service lifetime controls how long the container keeps and reuses an instance. There are three: Transient — a new instance every time it is requested; Scoped — one instance per request (per DI scope); Singleton — one instance for the whole application lifetime.
There are three lifetimes. Transient gives you a brand-new instance every time it's injected — good for small stateless helpers. Scoped gives you one instance per HTTP request, shared everywhere in that request — that's what DbContext uses, so all your repositories in one request talk to the same context and the same transaction. Singleton is one instance for the entire app, so it has to be thread-safe and is used for things like caches or configuration. The important rule is that you never inject a shorter-lived service into a longer-lived one — a singleton holding a scoped service is a captive dependency and it's a real source of bugs.
You choose the lifetime at registration:
AddTransient<T>()— created each time it is injected. Use for lightweight, stateless services.AddScoped<T>()— created once per HTTP request and shared within that request. Use for anything that should be consistent across a single request, e.g. EF CoreDbContext, a unit-of-work, the current-user accessor.AddSingleton<T>()— created once (either at first request or at registration if you pass an instance) and reused forever. Use for stateless services that are expensive to build or hold shared caches/config. Must be thread-safe.
A “scope” in a web app is created by the framework for each request and disposed at the end, which also disposes any IDisposable scoped/transient services created in it. In non-web code you create scopes yourself with IServiceScopeFactory.CreateScope().
The rule that gets tested: a longer-lived service must not depend on a shorter-lived one. A singleton that injects a scoped service captures the first instance and holds it forever — a captive dependency. The container will actually throw for the singleton→scoped case in the default configuration when scope validation is on.
builder.Services.AddSingleton<IClock, SystemClock>(); // stateless, app-wide
builder.Services.AddSingleton<IMemoryCache, MemoryCache>(); // shared cache
builder.Services.AddScoped<AppDbContext>(); // one per request
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<ICurrentUser, HttpContextCurrentUser>();
builder.Services.AddTransient<IPricingCalculator, PricingCalculator>(); // cheap, stateless
- Match an object's lifetime to the state it holds — per-call, per-request or app-wide
- Let the container create and dispose IDisposable services automatically
- Share one DbContext / unit-of-work across everything in a single request
- Avoid rebuilding expensive stateless services on every call (singleton)
Practise the whole thread the way an interviewer walks it — each question builds on the last.
- What is Dependency Injection in ASP.NET Core?
- What is loose coupling?
- What is Inversion of Control (IoC)?
- What are service lifetimes in ASP.NET Core?You are here
- What is the difference between Transient, Scoped and Singleton?
- Why is DbContext registered as Scoped?
- What is a captive dependency?