Suppose your app has a Logger class, and two different modules each write Logger() to create their own copy. Now you have two loggers — maybe writing to two different files, or buffering independently. A bug report comes in saying "the logs are incomplete," and the real cause is: there was never supposed to be more than one logger in the first place.
This is the exact pain Singleton solves — and, as you'll see by the end of this lesson, also the pain it can accidentally cause if used carelessly.
The pain: accidental duplicates
Nothing in the language stops this from happening by default:
logger_a = Logger() # module A creates one
logger_b = Logger() # module B creates a completely different one
# logger_a is logger_b -> FalseFor a Logger, a global app Config, or a connection pool, having two separate instances isn't just wasteful — it's a correctness bug waiting to happen. You want exactly one instance, reachable from anywhere.
The pattern: private constructor + getInstance()
Make the constructor unusable from outside the class, and offer one static "door in" that either builds the instance the first time, or returns the one it already built:
class Logger:
_instance = None
def __init__(self):
if Logger._instance is not None:
raise RuntimeError("Use Logger.get_instance() instead")
self.logs = []
@staticmethod
def get_instance():
if Logger._instance is None:
Logger._instance = Logger()
return Logger._instance
def log(self, message):
self.logs.append(message)
# usage anywhere in the codebase:
Logger.get_instance().log("user signed in")RefactoringGuru defines Singleton exactly this way: it "ensures that a class has only one instance, while providing a global access point to this instance." A country having one government is the usual real-world analogy — there is exactly one point of authority, and everyone routes through it.
Thread safety — the trap this lazy version has
The code above has a subtle bug under concurrency. If two threads both call get_instance() at nearly the same moment, both can see _instance is None at the same time, and both create their own object — exactly the bug Singleton was supposed to prevent, just moved one level down.
import threading
class Logger:
_instance = None
_lock = threading.Lock()
@staticmethod
def get_instance():
if Logger._instance is None: # first check, no lock (fast path)
with Logger._lock:
if Logger._instance is None: # second check, inside the lock
Logger._instance = Logger()
return Logger._instanceThe double check matters: the outer check avoids taking a lock on every single call (locks aren't free), and the inner check catches the race where two threads both passed the outer check before either acquired the lock.
When Singleton is the right call
- A shared logger every module writes to.
- An application-wide configuration object, loaded once.
- A connection pool, where duplicate pools would exhaust the database's connection limit.
- A cache that must stay consistent across the whole app.
Notice the pattern in all four: the requirement itself says "exactly one, shared everywhere" — Singleton isn't chosen because it's convenient, it's chosen because the requirement demands it.
When it's a disguised global variable
A User, a ParkingTicket, a Product — these are naturally many-instance objects. Making them Singletons doesn't solve a real problem; it just introduces hidden global state where none was needed.
Overused Singletons also make unit testing painful — shared, mutable state can leak between tests that run in the same process, and a class secretly reaching into Logger.get_instance() hides a real dependency that should have been passed in explicitly (connect this back to Dependency Inversion from the last lesson: a class quietly depending on a global Singleton is a dependency the constructor doesn't reveal).
Common mistakes
- Forgetting to make the constructor private/guarded — nothing then stops a second instance from being created directly.
- Lazy initialization with no thread safety in a multi-threaded environment.
- Using Singleton for objects that naturally have many instances (users, tickets, products).
- Hiding a real dependency behind a Singleton instead of passing it in explicitly (harder to test, harder to reason about).
Quick recap
| Idea | One-liner |
|---|---|
| Pain solved | Accidental duplicate instances of something that must be shared and unique. |
| Mechanism | Private constructor + a static getInstance() as the only door in. |
| Thread safety | Lazy init needs locking (or double-checked locking); eager init is safe by default. |
| Good fit | Logger, config, connection pool, shared cache. |
| Bad fit | Any naturally many-instance object (User, Ticket, Product). |
One thing to remember
Singleton is a controlled global variable — right when the requirement truly needs exactly one shared instance, wrong the moment it's used just because global access is convenient.
Practice Zone
Five MCQs, then two reasoning questions.
What problem does the Singleton pattern solve?
Asked in


What two things does a classic Singleton implementation need?
Asked in

Two threads call getInstance() at almost the same instant on a lazily-initialized Singleton with no locking. What can go wrong?
Asked in


Why do experienced engineers treat Singleton with more caution than the other four patterns in this course?
Asked in

Which of these is a reasonable real-world Singleton candidate?
Asked in

Think it through, then reveal:
A teammate makes every class in the codebase a Singleton "just to be safe." What's wrong with this instinct?
Asked in

What's the trade-off between lazy initialization (create the instance on first use) and eager initialization (create it when the class loads) for a Singleton?
Asked in

FAQ
Is Singleton the same as a static class?
Close, but not identical. A static class (all-static methods, no instance) can't implement an interface or be passed around as an object, and can't be lazily constructed or hold instance state cleanly. A Singleton is a real object — it can implement interfaces, be injected as a dependency, and be replaced with a mock in tests far more easily than a static class can.
Why do interviewers ask about thread safety for Singleton specifically?
Because it's one of the few patterns in this course where a subtle concurrency bug (the double-instance race condition) is easy to demonstrate and easy to miss — it's a natural, bounded way to test whether you think about concurrent access at all, without turning the whole interview into a concurrency deep-dive.
Can a Singleton be unit tested easily?
Less easily than a normal class, which is one of its real costs. Since the instance is shared and often global, tests can leak state into each other unless you add a reset hook for tests, or better, inject the Singleton as a dependency (via DIP) so tests can substitute a fresh fake instead of touching the real global one.
Singleton controlled how many instances exist. Next, we look at controlling which class gets instantiated in the first place — Lesson 4: Factory Pattern →


