Sitemap
Mir Rahman

Mir Rahman

Backend Developer | AWS Certified Solution Architect | Backend | AI | C#(.Net Core) | Python | Typescript | Microservices | SQL/NOSQL

Design Patterns — Singleton

4 min readJul 11, 2025

--

Press enter or click to view image in full size
One class, one object, no duplicates.

In software engineering, the Singleton Pattern is one of the simplest yet most widely used design patterns. Its primary purpose is to ensure that a class has only one instance throughout the application’s lifecycle and to provide a global point of access to that instance.

Why Use Singleton?

Imagine you’re building:

  • A logging service
  • A configuration manager
  • A cache manager
  • A database connection pool

All of these usually need exactly one shared instance to coordinate work across the entire system. Creating multiple instances could lead to inconsistencies or resource contention.

Get Mir Rahman’s stories in your inbox

Join Medium for free to get updates from this writer.

Here’s a simple thread-safe Singleton implementation in C#:

public class Logger
{
private static readonly Lazy<Logger> _instance = new Lazy<Logger>(() => new Logger());

// Private constructor ensures that new instances cannot be created from outside
private Logger() { }

public static Logger Instance => _instance.Value;

public void Log(string message)
{
Console.WriteLine($"Log: {message}");
}
}

A Few Real World Scenarios:

Lets check some scenarios that illustrate where and why the Singleton pattern makes sense in real-world software systems. It’s going to help us to understand the Singleton design pattern better.

Scenario 1: Application Configuration Manager

Imagine you’re building an enterprise .NET web application.
All parts of the system need to read global configuration settings — API keys, database connection strings, feature toggles, etc.

Problem:

If every request or component creates its own instance of the configuration loader, you’ll end up loading the same data from file or database multiple times.

Solution:

You can implement a ConfigurationManager class as a Singleton.
At application startup, it reads the configuration once and keeps it in memory.
Every part of your application just calls ConfigurationManager instance to get the same object and read the settings quickly.

Why this works:

The configuration never changes at runtime, so it’s safe to reuse one shared instance.

Related Story:

Company: E-commerce platform.

“Our app uses dozens of feature toggles and environment-specific settings stored in JSON files and secret vaults. Initially, every controller loaded config on demand, which added noticeable latency on cold requests.

So, we introduced a ConfigurationManager Singleton. It loads the configuration once at startup and caches it in memory. Controllers just call ConfigurationManager.Instance.Get(key).

We avoided duplicated file I/O, and deployments became safer because the config now stays read-only and shared.”

Scenario 2: Logging Service

Suppose you’re writing a backend service that handles thousands of requests per minute. Each request might logs debug and error information.

Problem:

If every request creates its own Logger instance, you might:

  • Waste memory.
  • Risk file access conflicts if multiple loggers write to the same file.

Solution

we can implement a Logger as a Singleton. All threads write logs through this single shared instance, which:

  • Buffers log messages asynchronously.
  • Handles file I/O safely.
  • Optionally sends logs to external systems.

Why this works:

Logging is a cross-cutting concern and doesn’t need separate loggers per request.

Related Story:

Company: FinTech startup building a payment API.

“When we first launched our payments API, each microservice created its own logger instance. Everything worked until we hit real traffic: the disk I/O went crazy, and logs appeared out of order because multiple logger objects tried writing to the same file at the same time.

We refactored and created a Logger Singleton. Now, every service calls Logger.Instance.Log(…). Internally, it buffers messages in memory and writes them asynchronously to disk. The log file finally stays consistent, and performance improved because we only have one background thread doing the write.”

Scenario 3: Database Connection Pool

Suppose you’re building an ASP.NET Core app that talks to MongoDB.

Problem:

Creating and disposing database connections per request is slow and expensive.

Solution:

You create a MongoClient instance as a Singleton. The MongoDB .NET driver internally manages a connection pool, your singleton holds it for the lifetime of the app.

Why this works:

The MongoDB driver is designed for thread safety, so sharing a single client instance is best practice.

Related Story:

Company: SaaS startup providing analytics dashboards.

“In the early version, every API call created its own MongoClient. Locally it was fine, but in production, CPU and memory usage spiked, and throughput dropped sharply.

Reading MongoDB docs, we realized MongoClient is thread-safe and designed to be reused. We wrapped it in a Singleton. Now all API controllers share the same client, leveraging Mongo’s internal connection pooling. Latency dropped from hundreds to just a few milliseconds.”

Scenario 4: Print Spooler

You’re building a desktop app for managing print jobs in an office.

Problem:

Multiple parts of the app (UI, background scheduler) need to send jobs to the printer. If each one had its own print queue, they’d conflict or send duplicated jobs.

Solution:

You implement a PrintSpooler as a Singleton. All components add jobs to the same spooler, which manages job order and sends them to the printer sequentially.

Why this works:

The printer is a single hardware resource, a central queue ensures proper coordination.

Related Story:

Company: Desktop software vendor for medical offices.

“Multiple parts of our app need to print: appointment lists, invoices, lab forms. At first, each module talked to the printer directly, and print jobs clashed or came out in weird order.

We fixed this by creating a PrintSpooler Singleton. Each module submits its jobs to PrintSpooler.Instance.QueueJob(…). The spooler sends jobs to the printer one by one, ensuring no conflicts and proper order.”

Summary:

Just remember, Singleton is perfect when:

  • You have a global resource or service.
  • Only one instance makes sense (shared configuration, logger, cache, client, spooler).
  • The class must coordinate access to limited resources.

--

Mir Rahman
Mir Rahman

Written by Mir Rahman

0 followers

Backend Developer | AWS Certified Solution Architect | Backend | AI | C#(.Net Core) | Python | Typescript | Microservices | SQL/NOSQL