Singleton Design Pattern: Comprehensive Explanation

The Singleton pattern is a creational design pattern that restricts a class to instantiate only one object and provides a global point of access to that instance. It ensures controlled access to a single shared resource.


Core Intent

“Ensure a class has only one instance and provide a global point of access to it.”

This is crucial when exactly one object is needed to coordinate actions across the system.


When to Use Singleton

ScenarioUse Case
Single shared resourceDatabase connection pool, logger, configuration
Global state is requiredApplication settings, cache, theme manager
Expensive to createThread pool, hardware interface
Controlled instantiationPrevent multiple instances
Testing with mocksReplace singleton in tests

Avoid when:

  • Used for global variables → leads to tight coupling
  • Makes unit testing difficult
  • Violates Single Responsibility Principle

Best Practice: Use Dependency Injection (DI) with scoped/singleton services instead of static singletons in modern applications.


Where It Is Used in Real Systems

DomainExample
LoggingLogManager.GetLogger()
ConfigurationAppSettings.Instance
CachingMemoryCache.Default
DatabaseDbConnectionFactory.Instance
IoC ContainersServiceProvider (scoped singleton)

Key Benefits

  • Controlled access to sole instance
  • Reduces memory footprint
  • Global access point
  • Lazy initialization support
  • Can subclass for variations

Real-World Example: E-Commerce Global Configuration Manager

An e-commerce platform needs one centralized configuration for:

  • Tax rates per region
  • Payment gateway credentials
  • Shipping rules
  • Feature flags

This configuration is:

  • Loaded once from config file / DB
  • Accessed globally by all services
  • Thread-safe in multi-user environment

C# Implementation (Thread-Safe, Lazy, Modern)

using System;
using System.Collections.Concurrent;

// ==================== CONFIGURATION MODEL ====================

public class TaxRule
{
    public string Region { get; set; }
    public decimal Rate { get; set; }
}

public class PaymentGateway
{
    public string Name { get; set; }
    public string ApiKey { get; set; }
    public bool IsSandbox { get; set; }
}

public class ShippingRule
{
    public decimal MinOrderValue { get; set; }
    public decimal Cost { get; set; }
    public bool IsFree { get; set; }
}

// ==================== SINGLETON ====================

public sealed class ECommerceConfiguration
{
    // Thread-safe lazy initialization (.NET 4+)
    private static readonly Lazy<ECommerceConfiguration> _instance 
        = new Lazy<ECommerceConfiguration>(() => new ECommerceConfiguration());

    // Public accessor
    public static ECommerceConfiguration Instance => _instance.Value;

    // Private constructor - prevents external instantiation
    private ECommerceConfiguration()
    {
        LoadConfiguration();
        Console.WriteLine("[Singleton] Configuration loaded and initialized.");
    }

    // Configuration data
    public ConcurrentDictionary<string, TaxRule> TaxRates { get; } = new();
    public PaymentGateway PrimaryGateway { get; private set; }
    public List<ShippingRule> ShippingRules { get; } = new();
    public bool IsMaintenanceMode { get; private set; }
    public string AppVersion { get; private set; } = "1.0.0";

    // Simulate loading from config file / DB
    private void LoadConfiguration()
    {
        // Tax Rules
        TaxRates["MH"] = new TaxRule { Region = "Maharashtra", Rate = 0.18m };
        TaxRates["KA"] = new TaxRule { Region = "Karnataka", Rate = 0.18m };
        TaxRates["DL"] = new TaxRule { Region = "Delhi", Rate = 0.18m };

        // Payment Gateway
        PrimaryGateway = new PaymentGateway
        {
            Name = "Razorpay",
            ApiKey = "rzp_live_abc123",
            IsSandbox = false
        };

        // Shipping
        ShippingRules.Add(new ShippingRule { MinOrderValue = 500m, Cost = 0m, IsFree = true });
        ShippingRules.Add(new ShippingRule { MinOrderValue = 0m, Cost = 50m, IsFree = false });

        IsMaintenanceMode = false;
    }

    // Optional: Allow controlled updates (e.g., hot reload)
    public void Refresh()
    {
        Console.WriteLine("[Singleton] Refreshing configuration...");
        LoadConfiguration();
    }

    // Prevent serialization attacks
    [System.Runtime.Serialization.OnDeserialized]
    private void OnDeserialized(System.Runtime.Serialization.StreamingContext context)
    {
        throw new InvalidOperationException("Deserialization of Singleton is not allowed.");
    }
}

// ==================== CLIENT USAGE ====================

public class OrderService
{
    private readonly ECommerceConfiguration _config;

    public OrderService()
    {
        _config = ECommerceConfiguration.Instance; // Global access
    }

    public decimal CalculateTax(string region, decimal subtotal)
    {
        if (_config.TaxRates.TryGetValue(region, out var rule))
            return subtotal * rule.Rate;
        return subtotal * 0.18m; // Default
    }

    public decimal CalculateShipping(decimal orderValue)
    {
        var freeRule = _config.ShippingRules.Find(r => r.IsFree && orderValue >= r.MinOrderValue);
        if (freeRule != null) return 0m;

        var standard = _config.ShippingRules.Find(r => !r.IsFree);
        return standard?.Cost ?? 50m;
    }

    public bool IsGatewayAvailable()
    {
        return !string.IsNullOrEmpty(_config.PrimaryGateway?.ApiKey);
    }
}

public class AdminPanel
{
    public void ToggleMaintenance()
    {
        var config = ECommerceConfiguration.Instance;
        config.GetType().GetProperty("IsMaintenanceMode")?
            .SetValue(config, !config.IsMaintenanceMode);
        Console.WriteLine($"[Admin] Maintenance Mode: {config.IsMaintenanceMode}");
    }
}

// ==================== DEMO ====================

class Program
{
    static void Main()
    {
        Console.WriteLine("=== SINGLETON PATTERN IN E-COMMERCE ===\n");

        // Multiple services use the SAME instance
        var orderService1 = new OrderService();
        var orderService2 = new OrderService();
        var admin = new AdminPanel();

        Console.WriteLine($"Same instance? {ReferenceEquals(
            ECommerceConfiguration.Instance, 
            ECommerceConfiguration.Instance)}"); // True

        decimal tax = orderService1.CalculateTax("MH", 1000m);
        decimal shipping = orderService1.CalculateShipping(600m);
        Console.WriteLine($"Tax: ₹{tax}, Shipping: ₹{shipping} (Free over ₹500)");

        Console.WriteLine($"Gateway: {ECommerceConfiguration.Instance.PrimaryGateway.Name}");

        // Admin can refresh config
        admin.ToggleMaintenance();
        ECommerceConfiguration.Instance.Refresh();

        // Prove singleton across threads
        var t1 = new Thread(() => Console.WriteLine($"[Thread1] Version: {ECommerceConfiguration.Instance.AppVersion}"));
        var t2 = new Thread(() => Console.WriteLine($"[Thread2] Version: {ECommerceConfiguration.Instance.AppVersion}"));
        t1.Start(); t2.Start();
        t1.Join(); t2.Join();
    }
}

Sample Output

=== SINGLETON PATTERN IN E-COMMERCE ===

[Singleton] Configuration loaded and initialized.
Same instance? True
Tax: ₹180, Shipping: ₹0 (Free over500)
Gateway: Razorpay
[Admin] Maintenance Mode: True
[Singleton] Refreshing configuration...
[Singleton] Configuration loaded and initialized.
[Thread1] Version: 1.0.0
[Thread2] Version: 1.0.0

UML Class Diagram

Participants in the Singleton Pattern

The classes and objects participating in this pattern include:

  • Singleton (ECommerceConfiguration)
    • Defines a static method (GetInstance) that returns the single instance.
    • Has a private constructor to prevent external instantiation.
    • Holds a static reference to the sole instance.
  • Client
    • Accesses the singleton via ECommerceConfiguration.GetInstance().

Summary Table

ParticipantRole in E-Commerce Example
SingletonECommerceConfiguration – one global config
ClientOrderService, AdminPanel – access via Instance

Thread-Safe Singleton Variants

ApproachCodeWhen to Use
Lazy<T> (Recommended)private static readonly Lazy<T> _instance = new(() => new T());.NET 4+, clean, thread-safe
Double-Checked Lockif (_instance == null) lock { if (_instance == null) _instance = new(); }Pre-.NET 4
Static Constructorstatic Singleton() { _instance = new(); }Eager loading
Nested Lazyprivate class Holder { static readonly T Instance = new(); }Java-style

Modern Alternative: Dependency Injection

// In Startup.cs (ASP.NET Core)
services.AddSingleton<ECommerceConfiguration>();

// Injected into services
public OrderService(ECommerceConfiguration config) { ... }

Preferred in enterprise apps.


Advantages in E-Commerce

BenefitImpact
Single config sourceNo duplication
Hot reloadUpdate without restart
Thread-safeSafe in web farms
Global accessAny module can read settings

Common Pitfalls

IssueSolution
Testing difficultyUse interface + DI
Memory leaksAvoid static event subscriptions
SerializationBlock with OnDeserialized
Global state bugsPrefer scoped services

Conclusion

The Singleton pattern is perfect for e-commerce configuration, logging, and caching where one controlled instance is required. Use .NET Lazy<T> for thread-safety.

Pro Tip: In modern systems, prefer DI containers (AddSingleton) over manual singletons for testability and flexibility.

Used in ASP.NET Core, Entity Framework, Serilog, and enterprise middleware.

Uma Mahesh
Uma Mahesh

Author is working as an Architect in a reputed software company. He is having nearly 21+ Years of experience in web development using Microsoft Technologies.

Articles: 276