Singleton
Some things in a program genuinely should only exist once: one configuration object, one connection pool, one logger writing to one file. The Singleton pattern is the answer to a very specific question: how do you guarantee, at the language level, that a class can never be instantiated more than once — no matter how many places in the codebase ask for it?
⚠️ The Problem
Imagine an application-wide logger. If any code can write new Logger(), nothing
stops five different modules from creating five separate Logger instances — each
possibly pointed at a different file handle, each buffering independently. Log lines from the
same request scatter across different instances, arrive out of order, or worse, two instances
both try to write to the same file and corrupt it. The bug isn't in any single line of code —
it's that "there should only ever be one of these" was never enforced anywhere, so eventually,
somewhere, a second one gets created by accident.
✅ The Solution
Make the constructor private (or otherwise unreachable from outside), and provide one
static method — conventionally called getInstance() — that creates the single
instance the first time it's called and returns that same instance every time after.
No code anywhere can construct a second one, because no code anywhere has access to the
constructor at all. The class enforces its own "only one" rule instead of trusting every
caller to remember it.
Example 1 — a logger, the classic case
class Logger {
static #instance;
constructor() {
if (Logger.#instance) {
throw new Error("Use Logger.getInstance() instead of new Logger()");
}
this.logs = [];
Logger.#instance = this;
}
static getInstance() {
if (!Logger.#instance) {
Logger.#instance = new Logger();
}
return Logger.#instance;
}
log(message) {
this.logs.push(message);
console.log(`[LOG] ${message}`);
}
}
const logger1 = Logger.getInstance();
const logger2 = Logger.getInstance();
logger1.log("Server started");
console.log(logger1 === logger2); // true — same object
Every call to Logger.getInstance(), from anywhere in the codebase, returns the
exact same object. logger1 === logger2 is true — there is only ever one
array of logs, one place log lines get written to, regardless of how many modules call
getInstance().
Example 2 — a secondary context: app configuration
class AppConfig:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.settings = {}
return cls._instance
def set(self, key, value):
self.settings[key] = value
def get(self, key):
return self.settings.get(key)
config_a = AppConfig()
config_a.set("theme", "dark")
config_b = AppConfig()
print(config_b.get("theme")) # "dark" — same underlying instance
Same idea in a different language and a different use case: application configuration read
from many unrelated parts of the app (the UI layer, the API layer, a background worker) all
need to see the same settings, updated in one place. Python's __new__
override achieves the identical guarantee that the private constructor achieved in the
JavaScript example.
👍 When To Use It
- Exactly one instance must coordinate actions across the whole app — a logger, a connection pool, a hardware interface (like a single printer spooler).
- You need lazy initialization — the instance is expensive to create and shouldn't be built until it's actually needed.
- Global access is genuinely required, and you want that access point to be explicit and controlled, not an ad-hoc global variable anyone can overwrite.
👎 When NOT To Use It
- When you're using it just to avoid passing a dependency around — that's usually a sign you want proper dependency injection instead, which keeps the dependency explicit and swappable (especially for tests).
- When you'll ever need more than one instance later — e.g. "one database connection" often becomes "one connection per tenant" as an app grows, and Singleton actively fights that change.
- In multi-threaded environments without care — a naive
getInstance()can create two instances if two threads call it at the exact same moment, defeating the whole point. - In tests — a Singleton's shared, persistent state tends to leak between test cases unless you add extra machinery to reset it, which is a common source of flaky test suites.
❓ FAQ
Isn't a Singleton just a global variable with extra steps?
Functionally, yes, they solve similar problems — but a Singleton controls construction (guaranteeing only one instance can ever be made) while a plain global variable controls nothing; anyone can reassign it or create a competing one. That said, this criticism is fair enough that many developers now prefer explicit dependency injection over Singleton for exactly this reason — see "When NOT To Use It" above.
How do you unit test code that depends on a Singleton?
With difficulty, which is part of why the pattern has fallen out of favor. Common
workarounds: expose a way to reset the instance between tests (e.g. a test-only
Logger.#resetForTests()), or better, inject the Singleton instance as a
constructor/parameter dependency into the classes that use it, so tests can substitute a
fake instance instead of touching the real global one.
Is Singleton thread-safe by default?
Not automatically. In languages with real threading (Java, C#, Go), two threads can both pass the "instance doesn't exist yet" check before either one finishes creating it, producing two instances. Thread-safe implementations add locking around creation, or use language features that guarantee one-time initialization (like Java's static initialization blocks, which the JVM guarantees run exactly once).
🙋 There Are No Dumb Questions
Q: If I need exactly one of something, why not just create it once and pass it
everywhere?
A: You often should — that's dependency injection, and many teams consider it a strictly
better solution to the "only one instance" problem than Singleton, precisely because it keeps
the dependency visible and swappable. Singleton is really for cases where threading that
instance through every constructor in the codebase would be impractical, and global,
controlled access is a deliberate tradeoff, not an accident.
✅ Quick Check
What does the Singleton pattern actually guarantee that a plain global variable does not?
Next up: what if you need one instance per type instead of one instance total — and you want to decide which concrete type to create without hardcoding it everywhere? That's the Factory Method pattern.