🧠 What Are Service Lifetimes?
In .NET Core, services registered in the Dependency Injection (DI) container can be configured with different lifetimes, which define how and when service instances are created and disposed.
The three built-in lifetimes are:
-
Singleton
-
Scoped
-
Transient
Choosing the right lifetime is critical for application performance, scalability, and correctness.
🔌 Real-Time Example: Email Notification System
Let’s assume you’re building an email notification system for an e-commerce website. This system includes:
-
A logging service to log every sent email.
-
A notification service that handles email delivery.
-
A repository that logs messages to a database.
Here’s how different lifetimes fit in:
Service | Description | Lifetime |
---|---|---|
LoggerService |
Global logger shared across all users | Singleton |
NotificationService |
Needs user-specific configurations | Scoped |
EmailFormatterService |
Lightweight, stateless string formatting | Transient |
🟢 1. Singleton
🧾 Description:
Created once and shared across the entire application lifetime.
🧪 Example:
💡 Real Use Case:
A LoggerService
that writes logs to a file or external system like ELK or App Insights.
All users and requests share the same logger instance.
🔵 2. Scoped
🧾 Description:
Created once per HTTP request. Same instance is reused throughout the request.
🧪 Example:
💡 Real Use Case:
A NotificationService
that uses HttpContext
to determine user-specific configurations like email templates.
Perfect for services involving user data or request context.
🟣 3. Transient
🧾 Description:
A new instance is created every time it’s requested.
🧪 Example:
💡 Real Use Case:
EmailFormatterService
that adds dynamic signatures or greeting lines. It’s stateless and lightweight.
Transient services are ideal for utility functions or non-shared tasks.
🖼️ Visual Diagram – .NET Core Service Lifetimes
⚠️ Warning: Mixing Lifetimes Improperly
You should NOT inject a Scoped or Transient service into a Singleton. It may lead to unexpected behavior or runtime exceptions.
✅ Workaround:
Use IServiceProvider
or a factory method inside the singleton:
🗂️ Summary Table
Lifetime | Instance Created | Scope | Best Use Cases |
---|---|---|---|
Singleton | Once for app lifetime | Global | Caching, logging, config reader |
Scoped | Once per request | Per user | Business logic, DB context, user service |
Transient | Every time | Per usage | Utilities, helpers, formatters |
💬 Interview Follow-Up Questions & Answers
❓ Q1: What happens if you inject a Scoped service into a Singleton?
🅰️ This causes a runtime error because Scoped services are tied to the request context. The singleton outlives the request scope.
❓ Q2: Can I inject Transient into Scoped or Singleton?
🅰️ Yes. But be cautious with Singletons because any stateful behavior in the Transient service may lead to unexpected side effects.
❓ Q3: Which service lifetime should I use for EF Core DbContext
?
🅰️ Scoped. EF Core DbContext
is not thread-safe and must be scoped per request.
❓ Q4: What’s the default lifetime in .NET Core?
🅰️ There is no default. You must specify it explicitly: AddSingleton
, AddScoped
, or AddTransient
.
❓ Q5: How does lifetime impact performance?
🅰️ Singleton reduces object creation overhead, while Transient increases it. However, Singleton can be risky if shared state is not managed carefully.
📌 Final Thoughts
Service lifetimes are fundamental in building scalable, testable, and maintainable applications in .NET Core. Start with Scoped for most services, Singleton for global state, and Transient for lightweight, disposable logic.