Dependency Injection Lifetimes in .NET Explained Simply: Transient, Scoped, and Singleton
Dependency Injection (DI) is one of the most important concepts in modern .NET applications.
Dependency Injection Lifetimes in .NET Explained Simply: Transient, Scoped, and Singleton
Dependency Injection (DI) is one of the most important concepts in modern .NET applications.

Dependency Injection Lifetimes in .NET (Photo — ChatGPT)
When we register a service in the DI container, we must decide how long the created object should live.
In .NET, there are three main service lifetimes:
- Transient
- Scoped
- Singleton
The easiest way to understand them is:
Transient = New object every time Scoped = One object per request Singleton = One object for the entire application lifetime
Now, let’s understand each one with simple examples.
What Is Dependency Injection?
Suppose we have a service:
public interface IMessageService
{
void SendMessage();
}
public class MessageService : IMessageService
{
public void SendMessage()
{
Console.WriteLine("Message sent");
}
}
Instead of creating the service manually:
var service = new MessageService();
we register it with the .NET Dependency Injection container:
builder.Services.AddTransient<IMessageService, MessageService>();
Then we can inject it into a controller:
public class HomeController : ControllerBase
{
private readonly IMessageService _messageService;
public HomeController(IMessageService messageService)
{
_messageService = messageService;
}
}
The DI container creates and provides the object automatically. But the important question is:
How long should that object live?
That is where DI lifetimes come into the picture.
1. Transient Lifetime
A Transient service creates a new instance every time the service is requested.
Registration:
builder.Services.AddTransient<IMessageService, MessageService>();
Imagine a hotel that gives you a new disposable paper cup every time you ask for water. Every request for a cup gives you a different cup. Transient works in a similar way.
For example:
Service requested → Object A
Service requested again → Object B
Service requested again → Object C
Every resolution creates a new instance.
When Should You Use Transient?
Transient is suitable for:
- Lightweight services
- Stateless services
- Small calculation services
- Formatting services
- Validation services
- Services that do not need to store request-specific state
Example:
builder.Services.AddTransient<ICalculatorService, CalculatorService>();
A calculator service usually performs a calculation and returns a result. It does not need to maintain state between calls.
Important Point
Transient does not necessarily mean one object per HTTP request. If the same transient service is resolved multiple times within a request, multiple instances can be created.
2. Scoped Lifetime
A Scoped service creates one instance for each scope. In a typical ASP.NET Core web application, one HTTP request creates one scope.
Registration:
builder.Services.AddScoped<IOrderService, OrderService>();
Suppose three users send requests:
Request 1
→ Object A
→ Object A
→ Object A
Request 2
→ Object B
→ Object B
Request 3
→ Object C
Inside Request 1, the same scoped instance is reused.
When Request 2 arrives, a new instance is created.
A simple real-life example is a restaurant table. Everyone sitting at one table shares the same water bottle.
Another table gets a different bottle.
Table 1 → Bottle A
Table 2 → Bottle B
Table 3 → Bottle C
Here:
Table = HTTP Request
Bottle = Scoped Service Instance
When Should You Use Scoped?
Scoped is commonly used for:
- Database operations
- Business services
- Repository classes
- Unit of Work implementations
- Request-specific state
A common example is EF Core’s DbContext.
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(connectionString);
});
In normal web application usage, the context is scoped to the request.
This is useful because operations performed during one request can work with the same context instance.
3. Singleton Lifetime
A Singleton service creates one instance for the application lifetime.
Registration:
builder.Services.AddSingleton<ICacheService, CacheService>();
The first time the service is created, that instance can then be reused by different requests.
For example:
Request 1 → Object A
Request 2 → Object A
Request 3 → Object A
Request 1000 → Object A
All requests use the same instance. Think about a shared notice board inside an office. All employees look at the same notice board. The company does not create a new notice board for every employee. That is similar to a Singleton service.
When Should You Use Singleton?
Singleton can be suitable for:
- Application-wide shared services
- Expensive-to-create, thread-safe services
- Configuration-style services
- Some caching services
- Services that safely maintain application-wide state
Example:
builder.Services.AddSingleton<IApplicationCache, ApplicationCache>();
Because the same object can be accessed by multiple requests at the same time, Singleton services must be designed carefully for thread safety.
Simple Comparison

Understanding with a GUID Example
One of the easiest ways to understand DI lifetime is by using a unique ID.
Create an interface:
public interface IOperationService
{
Guid Id { get; }
}
Implementation:
public class OperationService : IOperationService
{
public Guid Id { get; } = Guid.NewGuid();
}
Transient Registration
builder.Services.AddTransient<IOperationService, OperationService>();
If the service is resolved multiple times, you may see different IDs:
Service 1: a123...
Service 2: b456...
Different IDs indicate different objects.
Scoped Registration
builder.Services.AddScoped<IOperationService, OperationService>();
Within the same scope:
Service 1: a123...
Service 2: a123...
In a new HTTP request:
Service 1: c789...
A new request receives a new scoped instance.
Singleton Registration
builder.Services.AddSingleton<IOperationService, OperationService>();
Across requests:
Request 1: a123...
Request 2: a123...
Request 3: a123...
The same instance is reused.
A Common Interview Question: Can Singleton Depend on Scoped?
Consider this:
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddSingleton<INotificationService, NotificationService>();
Now imagine NotificationService depends directly on IUserService:
public class NotificationService : INotificationService
{
private readonly IUserService _userService;
public NotificationService(IUserService userService)
{
_userService = userService;
}
}
This lifetime relationship is problematic.
Why?
Because:
Singleton → Lives for the application lifetime
Scoped → Lives for one scope/request
A long-lived Singleton should not capture a shorter-lived Scoped dependency. This is commonly known as a captive dependency problem.
A useful rule is:
A longer-lived service should not directly capture a shorter-lived service.
For example:
Singleton → Scoped ❌
Singleton → Transient ⚠️ Requires careful consideration
Scoped → Singleton ✅
Transient → Singleton ✅
The exact behavior can depend on how and where services are resolved, so lifetime design should be based on ownership, state, disposal, and thread-safety requirements rather than memorizing combinations alone.
Which Lifetime Should I Choose?
Use this simple thought process:
Choose Transient when:
The service is lightweight, stateless, and a fresh instance is appropriate.
builder.Services.AddTransient<IEmailFormatter, EmailFormatter>();
Choose Scoped when:
The service belongs to one business operation or HTTP request.
builder.Services.AddScoped<IOrderService, OrderService>();
Choose Singleton when:
The service should be shared application-wide and is safe for concurrent access.
builder.Services.AddSingleton<IAppCache, AppCache>();
A Simple Memory Trick
Remember these three lines:
Transient: Every time, new object.
Scoped: Same object inside one request, new object for another request.
Singleton: Same object across the application lifetime.
Final Thoughts
Dependency Injection lifetimes control how long service instances live inside a .NET application. Choosing the correct lifetime is important for performance, state management, resource disposal, database access, and thread safety.
The three lifetimes can be summarized as:
Transient
↓
New instance every time it is resolved
Scoped
↓
One instance per scope, commonly one HTTP request
Singleton
↓
One instance for the application lifetime
If you are building an ASP.NET Core Web API, a common starting pattern is:
DbContext → Scoped
Repository → Scoped
Business Service → Scoped
Small Stateless Utility → Transient
Shared Thread-Safe Application Service → Singleton
The key is not to choose Singleton simply because creating fewer objects sounds faster. Choose the lifetime based on how the service manages state, resources, concurrency, and ownership.
Once you understand the lifetime of an object, choosing between Transient, Scoped, and Singleton becomes much easier.
메타데이터
- post_id
- b9e73a7e1887
- slug
- dependency-injection-lifetimes-in-net-explained-simply-transient-scoped-and-singleton-b9e73a7e1887
- url
- https://medium.com/crack-the-interview/dependency-injection-lifetimes-in-net-explained-simply-transient-scoped-and-singleton-b9e73a7e1887
- canonical_url
- https://medium.com/crack-the-interview/dependency-injection-lifetimes-in-net-explained-simply-transient-scoped-and-singleton-b9e73a7e1887
- author_url
- https://medium.com/@CodeCrack
- status
- ok
- fetched_at
- 2026-07-13 06:23:13