C# – Transient vs. Scoped vs. Singleton

By | 12/11/2025

In this post, we’ll see the three main service lifetimes in .NET: Transient, Scoped, and Singleton.
We’ll also touch on the concept of Dependency Injection (DI) and provide C# code examples to illustrate how each lifetime works.

WHAT IS DEPENDENCY INJECTION?
Dependency Injection is a design pattern that allows us to remove hard-coded dependencies and make our application loosely coupled, more extensible, and testable.
In a nutshell, instead of creating dependencies inside a class, we “inject” them from an external source, typically a DI container.
The DI container is responsible for creating and managing the lifecycle of these dependencies, which brings us to our main topic: service lifetimes.

UNDERSTANDING THE LIFETIMES
When we register a service in the DI container, we need to specify its lifetime.
The lifetime determines how long the service instance will live and how it will be shared across different parts of our application.
Let’s see the three main lifetimes:

[Transient]

  • What it is: A new instance of a transient service is created every time it’s requested from the DI container.
  • When to use it: Transient lifetimes are ideal for lightweight, stateless services. Think of services that perform a quick, isolated task and don’t need to maintain any state between requests.

[Scoped]

  • What it is: A new instance of a scoped service is created once per client request (connection). This means that within the same HTTP request, the same instance of the service will be shared across all the different places it’s injected.
  • When to use it: Scoped lifetimes are the go-to choice for services that need to maintain state within a single request. A classic example is the DbContext in Entity Framework Core. We want to use the same DbContext instance for the entire duration of a request to ensure data consistency.

[Singleton]

  • What it is: A single instance of a singleton service is created for the entire lifetime of the application. Every subsequent request for the service will get the same instance.
  • When to use it: Singleton lifetimes are perfect for services that are expensive to create, are thread-safe, and need to be shared across the entire application. Examples include logging services, caching services, and application configuration.

Now, let’s see these lifetimes in action with some C# code.


First, let’s define a simple service and its interface:

public interface IMyService
{
    Guid GetId();
}

public class MyService : IMyService
{
    private readonly Guid _id;

    public MyService()
    {
        _id = Guid.NewGuid();
    }

    public Guid GetId()
    {
        return _id;
    }
}

Now, let’s register this service with different lifetimes in our Program.cs file:

builder.Services.AddTransient<IMyService, MyService>();
// or
builder.Services.AddScoped<IMyService, MyService>();
// or
builder.Services.AddSingleton<IMyService, MyService>();

To see the difference, let’s inject this service into a controller and another service:

public class MyOtherService
{
    private readonly IMyService _myService;

    public MyOtherService(IMyService myService)
    {
        _myService = myService;
    }

    public Guid GetServiceId()
    {
        return _myService.GetId();
    }
}


[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
    private readonly IMyService _myService;
    private readonly MyOtherService _myOtherService;

    public MyController(IMyService myService, MyOtherService myOtherService)
    {
        _myService = myService;
        _myOtherService = myOtherService;
    }

    [HttpGet]
    public IActionResult Get()
    {
        var id1 = _myService.GetId();
        var id2 = _myOtherService.GetServiceId();

        return Ok($"Service ID 1: {id1}\nService ID 2: {id2}");
    }
}

Here’s what we will see with each lifetime:

  • Transient: id1 and id2 will be different because a new MyService instance is created for both the controller and MyOtherService.
  • Scoped: id1 and id2 will be the same because the same myService instance is shared within the scope of the HTTP request. If we make another request, we’ll get a new ID, but it will be consistent within that new request.
  • Singleton: id1 and id2 will be the same, and they will remain the same for every subsequent request until the application restarts.


Choosing the right service lifetime is a fundamental aspect of building well-architected .NET applications. By understanding the differences between Transient, Scoped, and Singleton, we can ensure that our application is efficient, scalable, and free of unexpected bugs.



Leave a Reply

Your email address will not be published. Required fields are marked *