The idea
What it is
Singleton is the simplest of all design patterns to state: make sure a class has exactly one instance, and give the whole program one easy way to get to it. That's it. Some things in a system are naturally one of a kind — there's one app configuration, one logger writing to one file, one connection pool — and it would be a bug to accidentally have two.
Picture an office with one shared printer. Everyone's desk can send jobs to it, and they all land in the same queue. Nobody 'makes a new printer' when they want to print — that would be absurd; you'd end up with a room full of half-used machines and your pages scattered across all of them. Instead, everyone reaches for the printer. A Singleton makes your class behave exactly like that shared printer: no matter how many times or from where you ask for it, you always get the one and only instance.
The one sentence to remember
A Singleton guarantees one instance and a global point of access to it. You don't create it with new from the outside — you ask the class for it (getInstance()), and it hands back the same object every single time.
Singleton has a bad reputation — and it's partly deserved
Singleton is famous and controversial. Because it's globally reachable and holds state, it can quietly couple your whole codebase together and make testing painful (every test shares the same object). Reach for it only when something is genuinely one-of-a-kind. When in doubt, prefer creating one instance at startup and passing it in where it's needed (dependency injection) — you get 'one instance' without the hidden global. We cover this honestly in the trade-offs below.
Mechanics
How it works
The three moving parts
A textbook Singleton does three small things. Together they make it impossible to get a second instance:
- A private constructor — so nobody outside the class can write
new Printer(). The door tonewis locked; only the class itself has the key. - A single stored instance — the class keeps one reference to itself in a private static field (
private static instance), the one and only copy. - A public accessor — a static method
getInstance()that everyone calls. The first call creates the instance and stashes it; every later call returns that same stored one.
class Printer {
private static instance: Printer; // the one stored copy
private queue: string[] = [];
private constructor() {} // 🔒 no 'new Printer()' from outside
static getInstance(): Printer { // everyone comes through here
if (!Printer.instance) { // first call? build it once
Printer.instance = new Printer();
}
return Printer.instance; // every call → the same object
}
print(job: string) { this.queue.push(job); }
}The magic is entirely in getInstance(). The first time anyone calls it, the if is true, so it builds the printer once and saves it. Every call after that, the if is false, so it skips straight to returning the already-built instance. Ask for it a thousand times from a thousand places — you get the exact same object a thousand times.
"Same object" means identity, not just equal
Two printers with empty queues might look equal, but a Singleton gives you the very same object in memory. That's why Printer.getInstance() === Printer.getInstance() is true — both names point at one thing. Anything you add to its queue from one place is instantly visible from every other place, because there's only one queue.
Lazy vs eager: when is it built?
There are two common styles, and the only difference is when the single instance gets created:
- Lazy — built on the first call to
getInstance()(the code above). You don't pay to create it until someone actually needs it. The catch: in a multi-threaded program, two threads could both see theifas true at once and each build one — that's the famous 'thread-safe Singleton' problem, solved with double-checked locking or a holder class (a later topic). - Eager — built once when the class first loads, before anyone asks. Dead simple and automatically thread-safe, but you pay the creation cost up front even if it's never used.
The easiest correct Singleton
In many languages the simplest safe Singleton isn't the classic getInstance() at all. In Java it's a single-element enum; in Python a plain module-level object (modules are imported once); in modern apps, one instance created at startup and injected where needed. The classic private-constructor version is the one interviewers ask about — but know that the boring alternatives are often the better real-world choice.
Why the 'without' world goes wrong
If the constructor is public and everyone writes new Printer(), each call makes a fresh printer with its own empty queue. Desk A prints to its printer, Desk B prints to a different one, and now your pages are split across machines nobody agreed on. The whole point of a shared resource — one queue, one config, one log file — is lost. The prototype lets you feel this directly: create three printers, watch the jobs scatter, then flip to the Singleton world and watch them all snap onto one machine.
Interactive prototype
See it. Build it. Break it.
A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.
About this simulation
An office with one shared printer. Flip between two worlds. In WITHOUT Singleton, every new Printer() makes a brand-new printer — click Get printer three times and you get three separate machines, each with its own job queue, so your print jobs scatter and nothing lines up. In WITH Singleton, every getInstance() hands back the same printer #1 — click it ten times and all the arrows point at one machine with one shared queue. Send a job from any desk and it lands in that single queue. The identity readout (printer === printer2 ?) flips from false to true the moment you switch worlds. One or two clicks and the whole idea lands: one instance, reachable from anywhere.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Live in the 'without' world first
Open the prototype with the toggle on WITHOUT Singleton. Click 'Get printer' three times — three separate printer boxes appear, each labelled #1, #2, #3. Send a print job from a desk and notice it only lands in whichever printer that desk grabbed. The identity check printer === printer2 reads false: these are genuinely different objects. This is the bug the pattern prevents.
Flip to the Singleton world
Switch the toggle to WITH Singleton. Now click 'Get printer' as many times as you like — every call draws an arrow back to the same printer #1. The identity check flips to true. Send jobs from different desks and watch them all pile into one shared queue. That's 'one instance, globally reachable' made visible.
Prove the shared state
Still in the Singleton world, send a job from Desk A, then read the queue from Desk B. Desk B sees Desk A's job, because there is only one queue. Try the same in the WITHOUT world and each desk sees only its own printer's jobs. This shared-state behaviour is exactly why Singletons are powerful — and exactly why they can be dangerous if overused.
In practice
When to use it — and what trips people up
Use a Singleton when the thing is truly one-of-a-kind
A Singleton fits a small set of cases where having two would be a bug, and where everyone genuinely needs to talk to the same one:
- A single shared resource — one connection pool, one thread pool, one hardware device driver. Two of these fighting over the same resource causes real bugs.
- One coordinating point — a logger writing to one file, an in-memory cache, an event bus. You want every part of the app to reach the same one.
- Read-mostly global config — app settings loaded once at startup and read everywhere. (If it changes often, a Singleton's shared mutable state gets risky.)
Prefer 'one instance' over 'Singleton pattern' when you can
You almost always want one instance — you rarely need the global access that makes Singletons troublesome. The cleaner path in modern code: create one instance at startup (in main, a container, or a module) and pass it to whatever needs it. You still have exactly one, but it's visible in the code, easy to swap for a fake in tests, and doesn't secretly couple everything together. See [[dependency-injection-and-ioc]] and [[program-to-interfaces]].
What it gives you
- Guarantees exactly one instance — impossible to accidentally create a second when duplication would be a bug (one queue, one config, one pool).
- One obvious access point — every part of the program reaches the same object without threading it through call after call.
- Lazy option saves work — the instance can be created only on first use, so you don't pay for it if it's never needed.
- Simple to state and recognise — it's the most widely known pattern, so
getInstance()instantly signals intent to other developers.
Common mistakes
- Hidden global state — any code can reach in and mutate the shared instance, coupling distant parts of the system in ways that are hard to trace.
- Hurts testability — tests share the one instance, so state leaks between them; you can't easily swap in a fake, and tests must reset it carefully.
- Thread-safety traps — lazy creation needs careful locking (double-checked locking / holder idiom) or two threads can each build an instance.
- Often overused — reached for as a convenient global rather than because the thing is truly one-of-a-kind; that's how it earned its bad name.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
class Printer {
private static instance: Printer; // the one and only stored copy
private queue: string[] = [];
private constructor() {} // 🔒 blocks 'new Printer()' outside
static getInstance(): Printer {
if (!Printer.instance) { // first call builds it once...
Printer.instance = new Printer();
}
return Printer.instance; // ...every call returns the same one
}
print(job: string): void { this.queue.push(job); }
jobs(): string[] { return this.queue; }
}
// Everyone asks the class — nobody uses 'new'.
const a = Printer.getInstance();
const b = Printer.getInstance();
a.print("report.pdf");
console.log(a === b); // true — same object
console.log(b.jobs()); // ["report.pdf"] — B sees A's job (one queue)References & further reading
5 sources- Articlerefactoring.guru
Singleton — Refactoring.Guru
The clearest illustrated walkthrough: the problem, the private-constructor + getInstance structure, real-world analogy, and language examples. Best first read.
- Booken.wikipedia.org
Design Patterns: Elements of Reusable Object-Oriented Software — Gamma, Helm, Johnson, Vlissides
The original 'Gang of Four' book that catalogued Singleton and 22 other patterns. The primary source for the pattern's formal intent and structure.
- Articletesting.googleblog.com
Why Singletons are controversial — Google 'Singletons are Pathological Liars'
Google's testing blog on how global Singletons hide dependencies and wreck testability, and why passing one instance in (dependency injection) is usually better.
- Articlebaeldung.com
Java enum Singleton — Effective Java (Joshua Bloch), Item 3
Why a single-element enum is the simplest thread-safe, serialization-safe Singleton in Java, with the classic getInstance variants compared.
- Docsen.wikipedia.org
Singleton pattern — Wikipedia
Concise reference covering lazy vs eager initialisation, thread-safety approaches across languages, and the common criticisms of the pattern.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Sign in to save your best score.
question 01 / 05
What does the Singleton pattern guarantee?
question 02 / 05
Why does a Singleton make its constructor private?
question 03 / 05
In a lazy Singleton, when is the single instance created?
question 04 / 05
If Printer is a proper Singleton, what does Printer.getInstance() === Printer.getInstance() return?
question 05 / 05
What is the most common, well-founded criticism of the Singleton pattern?
0/5 answered