Creational Pattern Pattern 1 of 23

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

Primary Example — JavaScript
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

Secondary Example — Python
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.

← Back to
Next →