| name | singleton |
| description | Teach and apply the Singleton pattern to ensure a class has exactly one instance with a global access point. Use when users ask about Singleton, single shared instance, thread-safe initialization, lazy vs eager instantiation, or comparing Singleton to dependency injection. |
Singleton
Purpose
Help the user decide whether Singleton is appropriate, then design and implement it correctly — including thread-safety, testability, and common pitfalls.
When To Use
- User asks to explain the Singleton pattern.
- A shared resource (database connection, config, logger, thread pool) must have exactly one instance.
- Global variables are being used but need protection against arbitrary overwriting.
- Lazy initialization of an expensive resource is required.
- User needs to control the number of instances of a class (Singleton is the single-instance special case).
Inputs
- The class or resource that should be restricted to one instance.
- Whether the environment is single-threaded or multi-threaded (drives thread-safety strategy).
- Whether initialization must be lazy (on first use) or eager (at startup).
- Language and framework constraints (affects static fields, class loaders, serialization).
- Testability requirements (drives whether pure Singleton or DI-managed singleton is preferred).
Workflow
- Clarify the goal.
Confirm whether the user wants explanation, code review, or implementation/refactor.
- Check applicability.
Validate the need: is a single shared instance truly required, or would dependency injection of a single instance be cleaner?
- Choose initialization strategy.
Select eager, lazy (null-check), double-checked locking, or initialization-on-demand holder based on thread-safety and startup cost.
- Implement the core structure.
Private constructor, private static instance field, public static
getInstance() / instance accessor.
- Address thread safety.
Apply the chosen strategy; call out language-specific guarantees (e.g., Go
sync.Once, Java volatile, Kotlin object).
- Address testability.
If unit testing matters, discuss abstracting behind an interface so tests can inject a substitute.
- Validate behavior.
Confirm that multiple
getInstance() calls return the same reference and that the constructor is unreachable from outside.
- Explain tradeoffs.
Call out SRP violation, global-state coupling, and test friction vs convenience.
Decision Branches
- If the language has a built-in singleton construct (Kotlin
object, Python module-level instance, Go package-level var with sync.Once):
Prefer the idiomatic language construct over a hand-rolled implementation.
- If testability is a hard requirement:
Recommend registering the singleton via a DI container as a shared scope rather than using the static getInstance pattern.
- If thread safety is required and initialization is expensive:
Use double-checked locking with
volatile (Java/C#) or initialization-on-demand holder idiom.
- If startup cost is negligible:
Prefer eager initialization — simpler and inherently thread-safe.
- If multiple related singletons are needed:
Consider whether Abstract Factory or a DI container manages them better than hand-rolled statics.
Output Contract
When responding, provide:
- A concise suitability verdict (why Singleton or why not).
- The chosen implementation strategy and why it fits the constraints.
- A minimal code sketch in the user's language showing the core structure.
- Thread-safety notes specific to the user's language/runtime.
- Testability guidance (interface extraction, DI alternative, or mock strategy).
- Validation checklist.
Quality Checks
- Constructor is private (or equivalent); no external
new calls possible.
getInstance() always returns the same instance reference.
- Thread-safe under concurrent first-call scenarios.
- Instance is not replaced or nulled out after initialization.
- If serializable,
readResolve() (or equivalent) prevents deserialization creating a second instance.
- Tests can run without depending on shared singleton state leaking between test cases.
Common Mistakes To Prevent
- Missing
volatile on the instance field in double-checked locking (Java/C#).
- Assuming module-level initialization is thread-safe without verifying language guarantees.
- Making the singleton hold mutable shared state without synchronization on individual methods.
- Using Singleton where dependency injection of a scoped/shared instance would be testable and cleaner.
- Forgetting that subclassing a Singleton breaks the uniqueness guarantee.
- Not resetting singleton state between unit tests, causing test order dependencies.
References