Rethinking the Singleton: Beyond Global State in Python
Overview
The Singleton design pattern ensures a class has only one instance while providing a global access point to it. While often dismissed as an antipattern due to the dangers of shared global state, its true utility lies in controlled instantiation. This tutorial explores how to implement singletons in Python, why they can fail in multi-threaded environments, and how to use them effectively for lazy loading expensive resources.
Prerequisites
To follow this guide, you should understand Python classes, dunder methods like __new__ and __call__, and basic concurrency concepts involving the threading module.

Key Libraries & Tools
- threading: A built-in Python module used to demonstrate and solve race conditions in instance creation.
- Metaclasses: A deep-level Python feature used to create generic, reusable singleton logic.
Code Walkthrough
The Metaclass Approach
Using a metaclass is the most robust way to implement a singleton. By overriding __call__, we intercept the class instantiation process.
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class DatabaseConfig(metaclass=SingletonMeta):
def __init__(self):
self.url = "sqlite:///dev.db"
Here, SingletonMeta maintains a dictionary of instances. When you call DatabaseConfig(), Python checks the dictionary first. If the instance exists, it returns it; otherwise, it creates one.
Making it Thread-Safe
In multi-threaded apps, two threads might check the dictionary simultaneously, creating two separate instances. We solve this with a double-checked locking pattern.
import threading
class SafeSingletonMeta(type):
_instances = {}
_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
Syntax Notes
- new vs call: Overriding
__new__handles instantiation at the class level, while__call__on a metaclass handles the "calling" of the class itself. - Modules as Singletons: In Python, a module is only initialized once per execution. For simple global state, a dedicated
.pyfile is more idiomatic than a class-based singleton.
Practical Examples
Controlled instantiation is perfect for Lazy Loading. Imagine loading a massive Large Language Model. You don't want to load it at startup—only when the first prediction occurs.
class ModelLoader(metaclass=SingletonMeta):
def __init__(self):
print("Loading 10GB Model...") # Only runs once
self.model = "Heavy Object"
Tips & Gotchas
- Testing Nightmare: Singletons create hidden dependencies. If a test modifies a singleton's state, every subsequent test inherits that change. Always provide a way to reset state for unit tests.
- Private Constructors: Unlike Java, Python cannot truly hide a constructor. Developers can always bypass your singleton logic by calling
__new__directly.
- Python
- 43%· products
- Bob Martin
- 14%· people
- Java
- 14%· products
- Large Language Model
- 14%· products
- Singleton design pattern
- 14%· products

The Real Reason the Singleton Pattern Exists
WatchArjanCodes // 15:31
On this channel, I post videos about programming and software design to help you take your coding skills to the next level. I'm an entrepreneur and a university lecturer in computer science, with more than 20 years of experience in software development and design. If you're a software developer and you want to improve your development skills, and learn more about programming in general, make sure to subscribe for helpful videos. I post a video here every Friday. If you have any suggestion for a topic you'd like me to cover, just leave a comment on any of my videos and I'll take it under consideration. Thanks for watching!