What is dependency injection in C# and how does the built-in container work?
Learn what dependency injection is in C# and how the built-in .NET container registers and resolves services with Transient, Scoped, and Singleton lifetimes.
Expected Interview Answer
Dependency injection (DI) is a design pattern where an object receives the services it depends on from an external provider instead of creating them itself, and .NET's built-in container (IServiceCollection/IServiceProvider) registers those services and resolves them automatically, honouring the lifetime you choose.
You register services against the IServiceCollection during startup, choosing a lifetime: Transient (a new instance per request), Scoped (one instance per scope, typically per web request), or Singleton (one instance for the app's lifetime). The container builds an IServiceProvider that constructs objects and injects their constructor dependencies recursively, resolving the whole graph for you. This decouples classes from concrete implementations, making code easier to test, swap, and maintain.
- Decouples classes from their concrete dependencies
- Makes unit testing easy via injected fakes or mocks
- Centralises object construction and lifetimes
- Encourages coding to interfaces
- Reduces boilerplate factory code
AI Mentor Explanation
Dependency injection is like a team manager handing each batter the right bat, gloves, and helmet before they walk out, instead of each player scrambling to find their own gear. The built-in container is that manager: you register what equipment exists once, and it delivers the correct kit to whoever needs it, tracking whether an item is shared for the whole match or fresh each innings.
Step-by-Step Explanation
Step 1
Define abstractions
Declare interfaces such as IEmailService so consumers depend on contracts, not concrete classes.
Step 2
Register services
In startup, add each mapping to IServiceCollection with a lifetime, e.g. services.AddScoped<IEmailService, SmtpEmailService>().
Step 3
Choose lifetimes
Pick Transient, Scoped, or Singleton based on how state and cost should be shared across requests.
Step 4
Build the provider
The framework builds an IServiceProvider that can resolve any registered service and its dependency graph.
Step 5
Inject via constructor
Declare dependencies as constructor parameters; the container supplies them automatically when it creates the class.
What Interviewer Expects
- Clear definition of inversion of control and DI
- The three lifetimes and when to use each
- Knowledge of IServiceCollection and IServiceProvider
- Preference for constructor injection
- Understanding of testability benefits
Common Mistakes
- Confusing Scoped and Singleton lifetimes
- Injecting a Scoped service into a Singleton (captive dependency)
- Using the service locator anti-pattern instead of constructor injection
- Registering concrete types with no abstraction
- Forgetting to dispose scopes created manually
Best Answer (HR Friendly)
“Dependency injection means a class is handed the helpers it needs instead of creating them itself, which keeps code loosely coupled and easy to test. In .NET a built-in container registers those helpers once and automatically supplies them wherever they are needed, managing how long each one lives.”
Code Example
// Program.cs - register services with lifetimes
builder.Services.AddSingleton<IClock, SystemClock>();
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
builder.Services.AddTransient<IEmailService, SmtpEmailService>();
// The container injects dependencies via the constructor
public class OrderService
{
private readonly IOrderRepository _repo;
private readonly IEmailService _email;
public OrderService(IOrderRepository repo, IEmailService email)
{
_repo = repo;
_email = email;
}
}Follow-up Questions
- What is the difference between Transient, Scoped, and Singleton?
- What is a captive dependency and how do you avoid it?
- How does constructor injection differ from the service locator pattern?
- How do you register multiple implementations of one interface?
- How would you resolve a service inside a Singleton safely?
MCQ Practice
1. Which lifetime creates a new instance every time the service is requested?
Transient services are instantiated fresh on each resolution, making them ideal for lightweight, stateless services.
2. Which interface do you use to register services in .NET DI?
You register services against IServiceCollection at startup; the framework builds an IServiceProvider from it to resolve them.
3. What problem is a captive dependency?
When a Singleton captures a Scoped or Transient dependency, that shorter-lived service is kept alive for the app's lifetime, causing bugs.
Flash Cards
What is dependency injection? — A pattern where a class receives its dependencies from an external provider instead of creating them itself.
Name the three .NET DI lifetimes. — Transient (new per request), Scoped (one per scope/request), Singleton (one per app).
Which interface resolves services? — IServiceProvider, built from the registrations in IServiceCollection.
What is a captive dependency? — A Singleton capturing a shorter-lived Scoped or Transient service, keeping it alive too long.
Continue Learning
Related Interview Questions
What is a captive dependency in .NET dependency injection, and how do you detect and prevent it?
hard
What are the boxing and unboxing operations in C# and why do they matter?
medium
What is the difference between managed and unmanaged code in C#?
medium
What is the IDisposable interface and the using statement in C#?
medium