Singleton Design Pattern
Question Variations
- "What is the Singleton design pattern, and what problem does it solve?"
- "How do you implement a thread-safe Singleton in your preferred language?"
- "When can the Singleton pattern be considered an anti-pattern?"
- "What is the difference between a static class and a Singleton?"
Why This Is Asked
The Singleton is one of the most well-known design patterns. Interviewers use it to check your understanding of object lifecycles, thread safety (Double-Check Locking), and your ability to recognize when a pattern might be an anti-pattern.
Key Concepts
- Purpose: Ensures a class has only one instance and provides a global point of access to it.
- Use Cases: Logging, Caching, Database Connection Pools, Configuration settings.
- Thread Safety: Crucial in multi-threaded environments to avoid creating multiple instances.
- Dependency Injection: Modern frameworks often manage singletons via the DI container, making manual Singleton implementations less common.
Question Variations
- “What is the Singleton design pattern, and what problem does it solve?”
- “How do you implement a thread-safe Singleton in your preferred language?”
- “When can the Singleton pattern be considered an anti-pattern?”
- “What is the difference between a static class and a Singleton?”
Answers by Technology
+ Add VariantExpected Answer (.NET 10 / C# 14)
A Singleton ensures a class has only one instance. In C#, a thread-safe implementation often uses Lazy<T>.
Why use it?: To manage shared resources like a Database Connection Pool, a Log Manager, or a global Configuration object.
Why It Matters
While Singletons provide a global state, they can make unit testing difficult. In modern .NET, we rarely implement the “Classic” Singleton pattern manually. Instead, we register the service with a Singleton Lifetime in the DI container.
Code Example
Classic Thread-Safe Implementation
public sealed class CacheManager
{
private static readonly Lazy<CacheManager> _instance =
new Lazy<CacheManager>(() => new CacheManager());
public static CacheManager Instance => _instance.Value;
private CacheManager() { } // Private constructor
}
Modern DI Implementation
// In Program.cs
builder.Services.AddSingleton<ICacheManager, CacheManager>();
Common Mistakes
- Not being thread-safe: A simple
if (instance == null) instance = new ...will fail in multi-threaded environments. - Hidden Dependencies: Using
MySingleton.Instancedeep inside business logic makes it hard to see what a class depends on. Use DI instead.
Follow-up Questions
- What is Double-Check Locking? (Answer: An older pattern used before
Lazy<T>to minimize lock overhead by checking the instance twice). - Is a Static Class a Singleton? (Answer: Similar, but a static class cannot implement interfaces or be passed as a parameter easily).
Expected Answer (Java 26)
A Singleton ensures only one instance of a class exists. In Java, there are several ways to implement it:
- Enum Singleton: Recommended by Joshua Bloch (Effective Java) because it is naturally thread-safe and resistant to serialization/reflection attacks.
- Double-Checked Locking: Using the
volatilekeyword to ensure visibility across threads. - Static Inner Class: Leveraging the JVM’s class-loading mechanism for thread-safe, lazy initialization.
Why It Matters
In Java enterprise development, most singletons are managed by the Spring IoC Container (using @Service or @Component). Understanding the manual implementation is important for core Java tasks or when working outside of a DI framework.
Code Example
Enum Singleton (Best Practice)
public enum DatabaseConnection {
INSTANCE;
public void connect() { /* ... */ }
}
Modern Spring DI
@Service // Default scope is Singleton
public class LoggingService {
public void log(String msg) { /* ... */ }
}
Common Mistakes
- Not using
volatile: In double-checked locking, withoutvolatile, another thread might see a partially initialized instance. - Reflection Attack: A manual singleton can be broken via reflection unless the constructor throws an exception if an instance already exists.
Follow-up Questions
- Is a Singleton in Spring the same as a GoF Singleton? (Answer: No. A GoF Singleton is one per ClassLoader; a Spring Singleton is one per IoC Container).
- Why use
Lazyinitialization? (Answer: To save memory and startup time if the object is expensive to create and might not be used).
Expected Answer (PHP 8.5)
A Singleton in PHP ensures a class is instantiated only once per request.
Why use it?: For objects that represent shared resources, like a Database connection (PDO) or a Configuration manager.
Why It Matters
In modern PHP, the “Manual Singleton” (private constructor + static instance) is often considered an anti-pattern. Instead, we use the Laravel/Symfony Service Container to manage singletons. This makes the code easier to test because the container can swap the singleton for a mock during testing.
Code Example
Manual Implementation (Traditional)
class Database
{
private static ?Database $instance = null;
private function __construct() {} // Private
public static function getInstance(): Database
{
return self::$instance ??= new self();
}
}
Modern Container Implementation (Laravel)
// In a Service Provider
$this->app->singleton(Connection::class, function ($app) {
return new Connection(config('db.dsn'));
});
Common Mistakes
- Expecting persistence across requests: PHP is “Shared Nothing.” A singleton only lasts for the duration of one HTTP request.
- Static state in tests: Manual singletons are hard to reset in unit tests, leading to “leaky” tests where one test affects the results of another.
Follow-up Questions
- What is the ‘Dependency Injection Container’? (Answer: A tool for managing class dependencies and performing dependency injection).
- Singleton vs Static Class? (Answer: A singleton can be instantiated (once) and can implement interfaces; a static class cannot).
Expected Answer (C++23)
For a process-wide singleton in modern C++, a function-local static is usually the simplest implementation. Since C++11, initialization of a local static is thread-safe: the object is initialized once, and callers wait if another thread is performing that initialization.
Prefer explicit dependency injection when practical because global state hides dependencies and makes tests harder. A singleton should have a clear lifetime and avoid order-dependent initialization across translation units.
#include <string>
class Settings {
public:
static Settings& instance() {
static Settings settings;
return settings;
}
std::string environment() const { return "production"; }
Settings(const Settings&) = delete;
Settings& operator=(const Settings&) = delete;
private:
Settings() = default;
};
int main() {
return Settings::instance().environment() == "production" ? 0 : 1;
}
Why It Matters
This pattern is common for process-wide configuration, registries, and logging, but it can create hidden coupling. Knowing the initialization guarantee avoids unsafe manual double-checked locking.
Common Mistakes
- Implementing manual lazy initialization without synchronization: Concurrent callers can create or observe partially initialized state.
- Using a non-virtual public destructor to imply manual ownership: The function-local static owns its lifetime; callers should not delete it.
- Using a singleton for ordinary dependencies: It makes test replacement and lifecycle control unnecessarily difficult.
Follow-up Questions
- When is the local static destroyed? (Answer: Normally during program termination, after
mainreturns orstd::exitis called.) - What is the static initialization order problem? (Answer: Initialization order of non-local statics across translation units is unspecified.)