AddTransient, AddScoped, AddSingleton: Differences in C# with Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

AddTransient, AddScoped, and AddSingleton: How Not to Get Confused by C# Service Lifecycles

\n\n

Have you ever caught yourself thinking: "Why is my service saving data between requests when I didn't ask for it?" Or the opposite — "Why is a new object created every time, and I'm losing state?"

\n\n

Welcome to the world of Dependency Injection (DI) in ASP.NET Core! This is one of the most powerful and simultaneously confusing topics for beginners. Today, we'll break down the three pillars that manage object creation in C#: AddTransient, AddScoped, and AddSingleton.

\n\n

By the end of this article, you'll not only understand the difference but also be able to confidently answer this question in an interview (yes, interviewers love asking it!). Let's go!

\n\n

What is a Service Lifecycle and Why Do You Need It?

\n\n

Imagine your application is a restaurant. Services are the chefs, waiters, and dishes. The lifecycle determines how long they "live":

\n\n
    \n
  • Transient — a disposable employee. Comes in, makes one bowl of soup, and leaves. A new order means a new employee.
  • \n
  • Scoped — an employee for one shift. Serves all customers while the restaurant is open (while the HTTP request lasts).
  • \n
  • Singleton — the head chef who works for the entire life of the restaurant. One for the whole application.
  • \n
\n\n

In C#, this logic is implemented through the built-in DI container. Let's look at the code.

\n\n

1. AddTransient — A New Instance Every Time

\n\n

When to use: Lightweight stateless services that don't store data between calls. Ideal for repositories, helpers, utilities.

\n\n

How it works: Every time you request the service (via a constructor or directly from the container), a new object is created.

\n\n
// Registration in Program.cs\nbuilder.Services.AddTransient<IMyService, MyService>();\n\n// Example service\npublic interface IMyService\n{\n    Guid GetInstanceId();\n}\n\npublic class MyService : IMyService\n{\n    private readonly Guid _id = Guid.NewGuid();\n    public Guid GetInstanceId() => _id;\n}\n\n// Controller that uses the service\npublic class HomeController : Controller\n{\n    private readonly IMyService _service1;\n    private readonly IMyService _service2;\n\n    public HomeController(IMyService service1, IMyService service2)\n    {\n        _service1 = service1;\n        _service2 = service2;\n    }\n\n    public IActionResult Index()\n    {\n        ViewBag.Id1 = _service1.GetInstanceId();\n        ViewBag.Id2 = _service2.GetInstanceId();\n        // Id1 and Id2 are DIFFERENT GUIDs!\n        return View();\n    }\n}\n
\n\n

Important: Even within a single HTTP request, if you inject IMyService into two different places, you will get two different objects. Memory is consumed, but there is no risk of data "contamination."

\n\n

2. AddScoped — One Instance Per Request

\n\n

When to use: Services that need to be consistent within a single HTTP request. For example, DbContext in Entity Framework Core, shopping cart services, request loggers.

\n\n

How it works: Within a single HTTP request (or scope), one instance is created. If you request it multiple times, you get the same object. But each new request from a different user gets its own instance.

\n\n
// Registration\nbuilder.Services.AddScoped<IScopedService, ScopedService>();\n\npublic interface IScopedService\n{\n    Guid GetInstanceId();\n}\n\npublic class ScopedService : IScopedService\n{\n    private readonly Guid _id = Guid.NewGuid();\n    public Guid GetInstanceId() => _id;\n}\n\n// In the same controller\npublic                

Blogs

Book Recommendations