Understanding Dependency Injection Lifetimes: Singleton, Scoped, and Transient

Published: (December 3, 2025 at 05:55 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

Singleton

Key Characteristics

  • One instance for the entire application lifetime
  • Shared across all requests
  • Created once when first requested
  • Disposed when the application shuts down

When to Use

  • Stateless services (no instance‑specific data)
  • Expensive‑to‑create services (caches, HTTP clients)
  • Shared resources (configuration, logging, caching)
// Register as singleton
services.AddSingleton();
services.AddSingleton();

Example: Cache Service

public class CacheService : ICacheService
{
    private readonly Dictionary _cache = new();
    private readonly object _lock = new();

    public void Set(string key, T value)
    {
        lock (_lock) { _cache[key] = value; }
    }

    public T Get(string key)
    {
        lock (_lock)
        {
            return _cache.TryGetValue(key, out var value) ? (T)value : default;
        }
    }
}

Watch Out For

  • Shared mutable state
  • Thread‑safety issues (ensure proper synchronization)
  • Implement IDisposable when holding unmanaged resources
// BAD: Singleton with mutable state
public class BadService
{
    public string CurrentUserId { get; set; } // Shared across all requests!
}

Scoped

Key Characteristics

  • One instance per scope (typically per HTTP request)
  • Shared within the same scope, different across scopes
  • Created when the scope begins
  • Disposed when the scope ends

When to Use

  • Database contexts (e.g., Entity Framework DbContext)
  • Request‑specific services that need state during a request
  • Unit‑of‑Work patterns
// Register as scoped
services.AddScoped();
services.AddScoped();

Example: Database Context and Order Service

public class ApplicationDbContext : DbContext
{
    public DbSet Orders { get; set; }
}

public class OrderService
{
    private readonly ApplicationDbContext _context;

    public OrderService(ApplicationDbContext context) => _context = context;

    public async Task CreateOrder(Order order)
    {
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();
        return order;
    }
}

Watch Out For

  • Injecting scoped services into singletons (causes lifetime mismatches)
// BAD: Singleton depending on scoped service
public class BadSingleton
{
    private readonly IScopedService _scoped; // Error!
}

// GOOD: Resolve scoped service via IServiceProvider
public class GoodSingleton
{
    private readonly IServiceProvider _serviceProvider;

    public GoodSingleton(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;

    public void DoWork()
    {
        using var scope = _serviceProvider.CreateScope();
        var scoped = scope.ServiceProvider.GetRequiredService();
        scoped.DoWork();
    }
}
  • Do not capture scoped services in background tasks without creating a proper scope.

Transient

Key Characteristics

  • New instance every time it’s requested
  • No sharing between consumers
  • Created on demand, disposed when out of scope (garbage collected)
  • Shortest lifetime of the three options

When to Use

  • Lightweight services that are cheap to create
  • Stateless operations
  • Services that must not be shared (sharing would cause bugs)
// Register as transient
services.AddTransient();
services.AddTransient, OrderValidator>();

Example: Order Validator

public class OrderValidator : IValidator
{
    public ValidationResult Validate(Order order)
    {
        var result = new ValidationResult();

        if (order.Items == null || order.Items.Count == 0)
            result.AddError("Order must have at least one item");

        return result;
    }
}

Watch Out For

  • Using transients for expensive‑to‑create services (performance impact)
  • Holding static references to transient instances (memory leaks)
  • Unnecessary creation when a singleton would suffice

Comparison Table

AspectSingletonScopedTransient
Instance CountOne for entire appOne per scopeNew every time
LifetimeApplication lifetimeScope lifetime (per request)Shortest – disposed when out of scope
Memory UsageLow (shared)MediumHigh (many instances)
Thread SafetyMust be thread‑safeUsually not requiredUsually not required
DisposalWhen app shuts downWhen scope endsWhen garbage collected
Typical UseStateless, expensive servicesRequest‑specific servicesLightweight, stateless services

Service Registration Example

public void ConfigureServices(IServiceCollection services)
{
    // Singleton: Shared across all requests
    services.AddSingleton();

    // Scoped: One per HTTP request
    services.AddScoped();

    // Transient: New instance each time
    services.AddTransient();
}

Background Worker Example (Scoped Service in a Hosted Service)

public class BackgroundWorker : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    public BackgroundWorker(IServiceProvider serviceProvider) =>
        _serviceProvider = serviceProvider;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Create a scope for each iteration
            using var scope = _serviceProvider.CreateScope();
            var scopedService = scope.ServiceProvider.GetRequiredService();
            await scopedService.DoWorkAsync();

            await Task.Delay(1000, stoppingToken);
        }
    }
}

Lifetime Dependency Rules

  • A service can only depend on services with equal or longer lifetimes.
Service LifetimeAllowed Dependencies
SingletonSingleton
ScopedSingleton, Scoped
TransientSingleton, Scoped, Transient

Common Mistake

// BAD: Singleton depending on scoped service
public class SingletonService
{
    private readonly IScopedService _scoped; // Error!
}

// GOOD: Resolve scoped service via IServiceProvider when needed
public class SingletonService
{
    private readonly IServiceProvider _serviceProvider;

    public SingletonService(IServiceProvider serviceProvider) =>
        _serviceProvider = serviceProvider;

    public void DoWork()
    {
        using var scope = _serviceProvider.CreateScope();
        var scoped = scope.ServiceProvider.GetRequiredService();
        // Use scoped service...
    }
}
Back to Blog

Related posts

Read more »

Convert Excel to PDF in C# Applications

Overview Transforming Excel files into polished, share‑ready PDFs doesn’t have to be a slow or complicated process. With the GroupDocs.Conversion Cloud SDK for...