Singleton by Jon Skeet clarification
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Jon Skeet’s singleton guidance is popular because it favors simple, correct patterns over lock-heavy boilerplate. The core idea is that CLR type initialization already provides thread-safe guarantees for static initialization. In most modern C# code, straightforward static initialization or Lazy<T> is the cleanest approach.
What a Singleton Should Guarantee
A singleton typically means:
- One instance per process.
- Global access point.
- Controlled construction path.
It does not automatically make all mutable operations thread-safe. Construction safety and runtime state safety are separate concerns.
Use singleton only when one logical shared instance truly represents domain design. If state should vary by request or user, singleton is usually wrong.
Simple Static Initialization Pattern
The simplest Skeet-aligned pattern is static readonly initialization.
Why this works:
- Type initialization runs once.
- CLR guarantees thread-safe static initialization.
- No manual lock complexity.
For most applications, this is enough.
Lazy<T> for Deferred Initialization
If instance creation is expensive and should be delayed until first use, Lazy<T> is a strong option.
This pattern preserves thread safety while avoiding startup cost until needed.
Nested Holder Pattern
Another classic approach uses a nested static type.
This is valid and lazy, though many teams now prefer Lazy<T> for readability.
Why Double-Checked Locking Is Usually Unnecessary
Legacy tutorials often show double-checked locking. It can be correct if done carefully, but it is harder to read and easier to misuse.
In most cases, replace it with static initialization or Lazy<T> and remove manual synchronization noise.
Simpler code with same guarantees is usually the best engineering tradeoff.
Singleton Versus Dependency Injection Lifetime
In DI-driven applications, you can often get singleton lifetime through container configuration instead of static access.
Benefits:
- Easier mocking in tests.
- Better control over composition root.
- Fewer hidden global dependencies.
If your project already uses DI extensively, container-managed singleton is often preferable.
Mutable State and Concurrency
Even with thread-safe construction, mutable singleton fields can still race.
Design mutable singleton state with explicit thread-safe structures and invariants.
Testing Considerations
Static singletons can leak state across tests. Mitigation options:
- Keep singleton immutable.
- Add controlled reset mechanism for test environment only.
- Prefer DI lifetimes in test-heavy codebases.
Testing pain is often a signal that static global access is overused.
Common Pitfalls
- Using singleton as default dependency pattern everywhere.
- Assuming singleton creation safety implies mutable-state safety.
- Adding heavy I/O work in singleton constructor and causing startup latency surprises.
- Reintroducing manual lock patterns without need.
- Choosing singleton for components that should be scoped or transient.
Summary
- Prefer simple CLR-backed static initialization for most singleton cases.
- Use
Lazy<T>when deferred creation is required. - Avoid manual double-checked locking unless constraints demand it.
- Treat mutable singleton state as a separate concurrency design problem.
- In DI-based systems, consider container singletons for better testability and explicit dependencies.

