Singleton
Singleton restricts a class to exactly one instance for its entire lifetime and gives every part of the program the same well-known way to reach that instance.
Problem
A logging module writes to one file on disk. If any part of the codebase can freely do `new Logger()`, nothing stops two different modules from opening independent file handles to the same log file - one write can silently clobber another, and buffered output can end up interleaved or lost.
What's needed is a guarantee, enforced by the class itself rather than by convention, that only one `Logger` will ever exist - plus a predictable way for distant, unrelated parts of the code to reach that one instance without threading a reference through every constructor and function signature between them.
Solution
Hide the constructor so nothing outside the class can call `new` on it directly, then add one static access method that the class itself controls. The first time that method runs, it builds the one permitted instance and stashes it in a static field; every call after that just hands back the same stashed reference instead of building anything new.
Because creation is funneled through a single method the class owns, the class can enforce "exactly one instance" as an invariant of its own code rather than relying on every caller to behave - and any part of the program can reach that instance by calling the same static method, with no reference-passing required.
When to Use
- Reach for it when a resource is inherently singular - a hardware handle, a single log file, a connection pool - and more than one instance would cause real conflicts, not just redundancy.
- Reach for it when many unrelated parts of the codebase need the same shared object and threading it through every layer as an explicit parameter would be impractical.
Real-World Examples
- **Node.js module caching** - `require()`/`import` returns the same module instance on every import within a process, effectively making top-level module state a singleton.
- **`java.lang.Runtime.getRuntime()`** - the JVM exposes exactly one Runtime object per process through a static access method, mirroring the classic Singleton shape.
- **Redux/Vuex application store** - a single global store instance holds the entire application's state tree, and components reach it through one shared access point rather than creating their own copies.
Structure
A single class owns both the hidden constructor and a static field holding its own one-and-only instance. A public static access method is the sole gateway to that field: it lazily builds the instance on first use and returns the cached reference on every call afterward.
Participants
Owns the private constructor, the static instance field, and the public static access method - combining state, creation, and access control in one class.
In the diagram: Singleton, the one shared instance.
class Singleton {
static #instance = null;
constructor() {
if (Singleton.#instance) {
throw new Error('Use Singleton.getInstance()');
}
Singleton.#instance = this;
}
static getInstance() {
if (!Singleton.#instance) {
new Singleton();
}
return Singleton.#instance;
}
doSomething() {
console.log('Singleton: doing something...');
}
}
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2);
Step 1 of 5
The static field that will hold the instance
A private static field starts out empty. It's the one place the class will stash its single permitted instance once one gets created.
Step 2 of 5
The constructor guards against a second instance
If the static field is already set, the constructor throws instead of finishing - so calling `new Singleton()` a second time can't silently produce a second object.
Step 3 of 5
getInstance() builds the instance once, then reuses it
The first call finds the static field empty and creates the instance. Every call after that skips creation entirely and just returns the same stashed reference.
Step 4 of 5
doSomething() is the instance's actual behavior
This method has nothing to do with the single-instance guarantee itself - it's just an example of the ordinary work a Singleton instance can do once obtained.
Step 5 of 5
Two calls, one shared instance
Both `s1` and `s2` come from calling `getInstance()`, and `s1 === s2` prints `true` - confirming that every caller reaches the exact same object, not a copy.
class Singleton {
private static instance: Singleton;
private constructor() {}
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
public doSomething(): void {
console.log('Singleton: doing something...');
}
}
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2);
public final class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
public void doSomething() {
System.out.println("Singleton: doing something...");
}
}
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
public sealed class Singleton
{
private static readonly Lazy<Singleton> lazy =
new Lazy<Singleton>(() => new Singleton());
private Singleton() { }
public static Singleton Instance => lazy.Value;
public void DoSomething()
{
Console.WriteLine("Singleton: doing something...");
}
}
Singleton s1 = Singleton.Instance;
Singleton s2 = Singleton.Instance;
Console.WriteLine(s1 == s2);
from threading import Lock
class SingletonMeta(type):
_instances = {}
_lock: Lock = Lock()
def __call__(cls, *args, **kwargs):
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
def do_something(self) -> None:
print('Singleton: doing something...')
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)
Step 1 of 5
The metaclass holds the shared cache and the lock
`_instances` is a dict that will cache one instance per class using this metaclass, and `_lock` is what will make building that instance safe when multiple threads call in at once.
Step 2 of 5
__call__ intercepts every Singleton() call
Because `SingletonMeta` is a metaclass, calling `Singleton()` actually runs this `__call__` method instead of going straight to normal object construction - so the class can decide whether to build a new instance or hand back the cached one.
Step 3 of 5
The lock makes lazy creation thread-safe
`with cls._lock` makes the check-then-create step atomic across threads, closing the race where two threads could both see no cached instance and each create their own.
Step 4 of 5
Singleton itself just opts into the metaclass
`Singleton` declares `metaclass=SingletonMeta` and otherwise looks like an ordinary class - `do_something()` is regular instance behavior, unrelated to the creation guarantee.
Step 5 of 5
Two calls, one shared instance
`s1` and `s2` both come from calling `Singleton()`, and `s1 is s2` prints `True` - confirming both names point at the one cached instance, not separate copies.
Click "Run" to execute this code in a sandboxed frame and see console output here.
TypeScript runs as plain JavaScript here - type annotations are stripped, not type-checked.
Click "Run" to execute this code in a sandboxed frame and see console output here.
Advantages
- The single-instance guarantee is enforced by the class itself, not by every caller remembering to reuse an existing object.
- Any part of the codebase reaches the same instance through one predictable method call, without passing a reference through layers of unrelated code.
- The instance is built only on first actual use, so its setup cost isn't paid at all if that part of the program never runs.
Disadvantages
- Anything using the singleton becomes implicitly coupled to it - that dependency doesn't show up in constructor or function signatures, making it easy to miss when reading the code.
- Swapping in a fake or mock version for tests is awkward, since consumers typically ask the class for its instance directly instead of receiving it as an injected dependency.
- In a multithreaded environment, naive lazy initialization can race and create two instances unless the creation path is explicitly synchronized.
- Global mutable state introduced by a singleton can make execution order matter in ways that are hard to reason about as the codebase grows.
Question 1 of 10
Why can naive lazy initialization be unsafe in a multithreaded environment?
Correct! Without synchronization, two threads can race through the same null-check before either one finishes creating the instance, so the single-instance guarantee breaks unless the creation path is explicitly synchronized.
Not quite. Without synchronization, two threads can race through the same null-check before either one finishes creating the instance, so the single-instance guarantee breaks unless the creation path is explicitly synchronized.
Question 2 of 10
In the Singleton participant's role, what three responsibilities does the class combine?
Correct! The Singleton owns the private constructor, the static instance field, and the public static access method - combining state, creation, and access control in one class.
Not quite. The Singleton owns the private constructor, the static instance field, and the public static access method - combining state, creation, and access control in one class.
Question 3 of 10
According to Singleton's pros, what happens to setup cost if the singleton's part of the program never runs?
Correct! The instance is built only on first actual use, so its setup cost isn't paid at all if that part of the program never runs.
Not quite. The instance is built only on first actual use, so its setup cost isn't paid at all if that part of the program never runs.
Question 4 of 10
In the logging example, what goes wrong if any part of the codebase can freely call `new Logger()`?
Correct! Nothing stops two different modules from opening independent file handles to the same log file - one write can silently clobber another, and buffered output can end up interleaved or lost.
Not quite. Nothing stops two different modules from opening independent file handles to the same log file - one write can silently clobber another, and buffered output can end up interleaved or lost.
Question 5 of 10
Why is the constructor made private (or hidden) in a Singleton?
Correct! If the constructor stayed public, any caller could bypass the access method and create a second instance, breaking the single-instance guarantee.
Not quite. If the constructor stayed public, any caller could bypass the access method and create a second instance, breaking the single-instance guarantee.
Question 6 of 10
What broader risk does global mutable state introduced by a Singleton create?
Correct! Global mutable state introduced by a singleton can make execution order matter in ways that are hard to reason about as the codebase grows.
Not quite. Global mutable state introduced by a singleton can make execution order matter in ways that are hard to reason about as the codebase grows.
Question 7 of 10
In the lazy-initialization version of Singleton, when is the one instance actually created?
Correct! The access method checks the static field, builds the instance only on the first call, and simply returns the cached reference on every call after that.
Not quite. The access method checks the static field, builds the instance only on the first call, and simply returns the cached reference on every call after that.
Question 8 of 10
What does Singleton guarantee about a class?
Correct! Take the Logger: however many places call the access method, they all get back the same instance writing to the same file handle - no two competing Loggers can silently clobber each other's writes.
Not quite. Take the Logger: however many places call the access method, they all get back the same instance writing to the same file handle - no two competing Loggers can silently clobber each other's writes.
Question 9 of 10
According to when_to_use, what kind of resource is Singleton actually meant for?
Correct! Reach for Singleton when a resource is inherently singular - a hardware handle, a single log file, a connection pool - and more than one instance would cause real conflicts, not just redundancy.
Not quite. Reach for Singleton when a resource is inherently singular - a hardware handle, a single log file, a connection pool - and more than one instance would cause real conflicts, not just redundancy.
Question 10 of 10
What is a common drawback of using Singleton?
Correct! Anything using the singleton becomes implicitly coupled to it - that dependency doesn't show up in constructor or function signatures, and swapping in a mock for tests is awkward as a result.
Not quite. Anything using the singleton becomes implicitly coupled to it - that dependency doesn't show up in constructor or function signatures, and swapping in a mock for tests is awkward as a result.