Some things should exist exactly once in a running application: a connection pool, a configuration manager, a logger. Singleton guarantees a class has one instance and gives everyone a single way to reach it. It's also the most overused pattern, so knowing when not to use it matters as much as knowing how.
Intent#
Ensure a class has only one instance, and provide a global point of access to it. (GoF)
Analogy: a country has one president at a time. Two simultaneous presidents would mean conflicting orders.
The problem#
Without any control, every part of the app creates its own connection:
class DatabaseConnection:
def __init__(self):
print("Creating expensive database connection...")
# Different parts of the app each make their own
conn1 = DatabaseConnection()
conn2 = DatabaseConnection()
conn3 = DatabaseConnection()
# Three connections: wasted resources
A global variable gives global access but doesn't stop anyone from calling the constructor again. Multiple instances waste resources and drift into inconsistent state.
Typical candidates: connection pools, loggers, configuration managers, caches, print spoolers, device drivers.
Structure#
Three ingredients:
- Controlled creation, so calling the class doesn't always build a new object. In Python you override
__new__, since there are no private constructors. - A class attribute holding the single instance (it belongs to the class, not to an object).
- One way in: the class call itself, or an accessor such as
get_instance().
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
# Created on the first call (lazy)
cls._instance = super().__new__(cls)
return cls._instance # the same object every time
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True
This basic version is not thread-safe: two threads can both see None and both create an instance.
Implementation options#
There are several ways to write a singleton in Python, with different thread-safety and laziness:
| Variant | Thread-safe | Lazy | Notes |
|---|---|---|---|
Basic __new__ |
No | Yes | Race condition on first access |
| Lock on every call | Yes | Yes | Every call pays for the lock |
| Double-checked locking | Yes | Yes | Lock only while creating |
| Module-level instance | Yes | No | Simplest; created on first import |
| Cached factory function | Mostly | Yes | @functools.cache; may build twice under a race |
Lock on every call#
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
with cls._lock: # correct, but every call takes the lock
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Double-checked locking#
This is the classic answer: check without the lock, then check again with it.
import threading
from typing import Optional
class DatabaseConnectionPool:
_instance: Optional["DatabaseConnectionPool"] = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
# 1st check, no lock (fast path)
if cls._instance is None:
with cls._lock:
# 2nd check: another thread may have won
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, connection_url: str = "default_url"):
# __init__ runs on EVERY DatabaseConnectionPool(...) call,
# so only initialize once
if not self._initialized:
self.connection_url = connection_url
self._initialized = True
def execute_query(self, query: str) -> str:
return f"Executing '{query}' on {self.connection_url}"
a = DatabaseConnectionPool("postgres://prod")
b = DatabaseConnectionPool("postgres://other") # ignored: already set up
print(a is b, b.connection_url) # True postgres://prod
The lock prevents two threads from creating two instances at the same moment; the outer check avoids taking the lock on every access.
Module-level instance (the Pythonic way)#
Python caches imported modules in sys.modules, so a module's top-level code runs only once per process. An object created there is effectively a singleton, and the import lock makes it thread-safe.
# database.py
class DatabaseConnectionPool:
def __init__(
self, connection_url: str = "postgresql://localhost:5432/mydb"
):
self.connection_url = connection_url
print("Initializing connection pool (happens once)")
def execute_query(self, query: str) -> str:
return f"Executing '{query}' on {self.connection_url}"
db_pool = DatabaseConnectionPool() # the single shared instance
# service.py
from database import db_pool # import the object, not the class
class UserService:
def get_user(self, user_id: int) -> str:
# Real code should use parameterized queries, not f-strings
return db_pool.execute_query(
f"SELECT * FROM users WHERE id = {user_id}"
)
class OrderService:
def get_order(self, order_id: int) -> str:
return db_pool.execute_query(
f"SELECT * FROM orders WHERE id = {order_id}"
)
Trade-off: the instance is created at import time even if nobody uses it, imports get slower, and exceptions during creation surface as import errors.
Cached factory function#
To make the module-level approach lazy, wrap creation in a cached function:
from functools import cache
class Config:
def __init__(self):
print("Loading config (happens once)")
@cache
def get_config() -> Config:
return Config()
print(get_config() is get_config()) # True
functools.cache won't corrupt itself under threads, but two threads that miss at the same moment can both run Config(). Add a lock if creation must happen exactly once.
In an interview, a good answer is to write the double-checked locking version, then mention that idiomatic Python often just uses a module-level object.
Choosing eager vs lazy#
| Use eager when... | Use lazy (double-checked locking or a cached function) when... |
|---|---|
| It will definitely be used | It might never be used (e.g. a report generator) |
| Creation is cheap (loading a few properties) | Creation is expensive (loading a large dataset) |
| Slower startup is acceptable | Creation might fail (e.g. the network is down) and you want to handle it at use time |
How singletons break#
Python has no private constructors, so a class-based singleton is an honor system. A few ways around it:
| Attack | What happens | Defense |
|---|---|---|
Bypassing __new__ |
object.__new__(Singleton) builds a second instance |
None really; treat it as a convention |
| Copying | copy.copy() on a plain module-level object makes a second one |
Define __copy__ and __deepcopy__ to return self |
| Pickling | Unpickling a plain object builds a new one from bytes | Define __reduce__ to point back at the shared instance |
A singleton built by overriding __new__ survives copy and pickle, because both call __new__ and get the existing object back (though unpickling overwrites its attributes). A plain module-level object does not.
Once there are two "singletons", each has its own state. You lose the single source of truth, resources multiply, access-control singletons can be bypassed, and locks that assumed one object stop protecting anything.
Enum members are unique by design, and copy and pickle both return the existing member, so some codebases use a one-member Enum as a singleton.
Why Singleton is often called an anti-pattern#
Singleton solves one problem but often creates others.
- Hidden dependencies. A class that calls
Logger.get_instance()inside a method doesn't show that dependency in its constructor or API. - Global state. Any code can change it, so bugs are hard to trace. It's a global variable with better manners.
- Hard to test. Tests share one instance, so state leaks between them and test order starts to matter. You can't easily substitute a mock.
- Two responsibilities. The class manages its own lifecycle and does its real job, which violates the Single Responsibility Principle.
- Tight coupling. Every
get_instance()call is hard-wired to one concrete class.
"One" only means one per process#
Each Python process gets its own singleton. Run ten replicas of a service and you have ten "singletons", each with its own state:
| Service replica | counter |
|---|---|
| 1 | 100 |
| 2 | 45 |
| 3 | 78 (which one is right?) |
If you truly need one thing across a distributed system, use external coordination (a database, Redis, ZooKeeper), not a singleton.
Never for per-user state#
A singleton shopping cart means every user shares one cart: user A adds a phone, user B adds a laptop, and both see both items at checkout. The same applies to user sessions, transactions and request contexts. Scope these per session or per request, or store them keyed by user ID.
Prefer dependency injection#
Instead of reaching for get_instance(), pass the shared object in through the constructor:
# Hidden dependencies
class HiddenOrderService:
def process_order(self) -> None:
db = Database.get_instance()
log = Logger.get_instance()
# Explicit, testable, swappable
class OrderService:
def __init__(self, db: "Database", log: "Logger"):
self._db = db
self._log = log
Your main() (or a DI container) can still make sure only one Database exists, but your classes no longer know or care. You can also hide a singleton behind an interface (an ABC or Protocol such as Logger) so tests can pass a MockLogger.
When to use it#
Use a singleton only when:
- Exactly one instance is genuinely required: a hardware interface, a connection pool, a cache.
- Application-wide shared resources: logging, configuration.
When not to use it#
Avoid it in these cases:
- Only because you want global access. Inject the dependency instead.
- For user-specific or request-specific data.
- For stateless utilities. Plain functions (
math.sqrt) are simpler. - When "only one" might become "several" later (per-tenant config, multiple databases).
Pitfalls#
Watch for these mistakes:
- Lazy initialization without thread safety.
- Forgetting the second check inside the lock in double-checked locking.
- Storing mutable per-user data in it.
- Making something a singleton that has no reason to be one (a
Calculatorwith no state).
Common interview questions#
These come up often:
- How can a singleton be broken? Bypassing
__new__, copying, pickling. - How do you prevent it? Override
__new__(copy and pickle then return the same object), define__copy__/__deepcopy__/__reduce__, or use a one-memberEnum. - Singleton vs a module of functions? A singleton is an object: it can implement an interface, be passed as a parameter, be lazily created and be swapped for a mock.
Key takeaways#
- Controlled creation (
__new__) + a class attribute holding the instance + one way in = Singleton. - Use a lock (double-checked locking) if several threads can create it at once.
- A module-level instance is the idiomatic Python singleton.
- "Single" means single per process, never across a distributed system.
- Singletons hide dependencies and hurt testing; dependency injection usually gives you one instance without those costs.