The Singleton Design Pattern: A Double-Edged Sword

Tobias Lang
5 min readApr 30, 2023

Introduction

The Singleton design pattern is a classic creational pattern in the field of software design, with its roots dating back to the early days of object-oriented programming. It is used to ensure that a class has only one instance, and provides a global point of access to this instance.

This article will introduce the Singleton pattern, provide some actual code, discuss why it’s considered an anti-pattern, and offer examples of its use in the Python and Java standard libraries. However, I will focus on implementing the pattern in Python and not show you the original design proposed by the Gang of Four¹. If you are interested have a look at their excellent book. The sample code shown in the article can be found on my Github repository.

The article is part of my series on Software Design Patterns. Have a look at the overview article: https://medium.com/@tobiaslang/software-design-patterns-an-introduction-7fb3f9e845cd.

Stormtroppers standing in a spacedock.
Photo by Brian McGowan from Unsplash

Singleton Design Pattern

The Singleton pattern is most likely the easiest of all the patterns as proposed by the Gang of Four¹ in essence looks like this:

UML of the Singleton design pattern.
Trashtoy, Public domain, via Wikimedia Commons

As you can see, the Singleton

  • creates and manages the single object of the class and stores it in a private variable (singleton)
  • provides global access to this object via an instance operation (getInstance())

A basic implementation in Python looks like this (don’t worry, we will simplify it):

class Singleton:
_singleton = None

def __init__(self) -> None:
raise RuntimeError("Call get_instance() instead")

@classmethod
def get_instance(cls) -> Singleton:
if cls._singleton is None:
cls._singleton = cls.__new__(cls)
return cls._singleton

Singleton1 = Singleton.get_instance()
singleton2 = Singleton.get_instance()

print(singleton1 is singleton2) # Output: True

The __init__ method of the class raises a RuntimeError with a message that instructs the user to call the get_instance method instead of directly creating an instance of the class. The get_instance method is a class method that returns the _singleton instance of the class. If the _singleton attribute is None, the __new__ method is called on the class, which creates a new instance of the class and sets the _singleton attribute to this new instance.

Simple Singleton

The above implementation looks complicated since we lack private and public methods and are not creating objects using the new keyword (like in C++). An elegant and quite simple solution to the Singleton pattern utilizes the __new__ method and gets rid of the Runtime Error:

class SingletonSimple:
_instance = None

def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance

singleton1 = SingletonSimple()
singleton2 = SingletonSimple()

print(singleton1 is singleton2) # Output: True

In this example, we override the __new__ method to control the object creation process. Whenever a new instance is requested, the _instance class attribute is checked. If it's None, a new instance is created using the super().__new__() method. Otherwise, the existing instance is returned. This takes advantage of the fact that only one instance of a class attribute (_instance) exists.

Singleton via Borg

The Singleton via Borg pattern, proposed by Alex Martelli, is a creative alternative to the traditional Singleton pattern in Python. Instead of restricting the creation of instances, the Borg pattern ensures that all instances of a class share the same state. This is achieved by using a shared dictionary for the __dict__ attribute, which stores the object's state:

class Borg:
_shared_state = {}

def __init__(self):
self.__dict__ = self._shared_state

borg1 = Borg()
borg2 = Borg()

borg1.x_value = 42

print(borg1.x_value) # Output: 42
print(borg2.x_value) # Output: 42
print(borg1 is borg2) # Output: False

In this example, we define the _shared_state class attribute as an empty dictionary. In the __init__ method, we set the instance's __dict__ attribute to reference the _shared_state. As a result, any modification to one instance's state will be reflected in all instances of the class. The Borg pattern provides an elegant solution for sharing state across multiple instances while avoiding some of the drawbacks associated with the traditional Singleton pattern, such as global state management and inflexibility.

Why Singleton is Considered an Anti-Pattern

Despite its popularity, the Singleton pattern has its share of detractors who argue that it’s an anti-pattern. Here are some reasons why:

  • Global State: Singletons act as a global state, making it difficult to reason about code and test individual components in isolation. This can lead to bugs that are hard to detect and fix.
  • Inflexibility: Singletons can make it difficult to modify code, as they create a tight coupling between the Singleton class and the classes that depend on it. This can hinder future code refactoring or extension.
  • Concurrency Issues: When working with multithreaded applications, Singleton instances may cause synchronization problems, resulting in race conditions and other concurrency-related issues.

Bear these points in mind, if you think about implementing the Singleton pattern. There are valid use cases for the Singleton pattern. Furthermore, you know your own codebase best and can make the most educated decisions, about when to use a Singleton and when to avoid it. Because in the end, design patterns are just ready-to-use solutions to common development issues.

Real-life Use of the Singleton

Despite the drawbacks, the Singleton pattern is still used in various standard libraries of programming languages, including Python and Java.

Python logging module

In Python’s standard library, the logging module uses the Singleton pattern for its Logger class. This ensures that each named logger is unique, and the same logger instance is returned for the same name. This makes it easier to configure and manage loggers throughout the application.

import logging

logger1 = logging.getLogger("example")
logger2 = logging.getLogger("example")

print(logger1 is logger2) # Output: True

Java java.lang.Runtime

In Java’s standard library, the Runtime class is an example of a Singleton. It represents the Java runtime environment and provides methods for executing external processes, garbage collection, and querying system properties. To ensure that there is only one instance of Runtime, it uses the Singleton pattern.

javaCopy code

Runtime runtime1 = Runtime.getRuntime();
Runtime runtime2 = Runtime.getRuntime();

System.out.println(runtime1 == runtime2); // Output: true

Conclusion

The Singleton design pattern is a widely used creational pattern that ensures a class has only one instance and provides a global point of access to that instance. However, due to its global state nature and the tight coupling it creates, it is often considered an anti-pattern. Nonetheless, the Singleton pattern is still used in the standard libraries of popular languages like Python and Java. Developers should carefully weigh the pros and cons of using the Singleton pattern and consider alternative patterns if possible.

¹ Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides

--

--