Introduction
The Singleton pattern ensures a class has only one instance and provides a global point of access to it. It is one of the most widely used — and misused — design patterns in software engineering. When applied correctly, it solves real problems around shared resource management, such as configuration settings, connection pools, and logging systems.
The Problem
Imagine your application needs a centralized configuration manager that reads settings from a file or environment variables. If every part of your codebase creates its own instance, you end up with duplicated I/O, inconsistent state, and wasted memory. Worse, if one module changes a setting in its copy, other modules never see the update. You need a single, shared source of truth.
The Solution
The Singleton pattern solves this by restricting instantiation to a single object. The class itself controls its own creation, stores the instance privately, and exposes it through a static access method. Every caller gets the same instance, guaranteeing consistent state across the entire application.
Key Principle
Controlled access to a sole instance. The class owns its lifecycle — no external code can create or destroy additional instances. This gives you a single coordination point without relying on global variables.
Java Example
This example implements a thread-safe ConfigurationManager that loads application settings once and provides them everywhere.
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class ConfigurationManager {
// volatile ensures visibility across threads
private static volatile ConfigurationManager instance;
private final Properties properties;
// Private constructor prevents external instantiation
private ConfigurationManager() {
properties = new Properties();
try (FileInputStream fis = new FileInputStream("app.properties")) {
properties.load(fis);
System.out.println("Configuration loaded from app.properties");
} catch (IOException e) {
System.err.println("Failed to load config: " + e.getMessage());
// Load defaults
properties.setProperty("db.host", "localhost");
properties.setProperty("db.port", "5432");
properties.setProperty("app.name", "MyApplication");
}
}
// Double-checked locking for thread safety
public static ConfigurationManager getInstance() {
if (instance == null) {
synchronized (ConfigurationManager.class) {
if (instance == null) {
instance = new ConfigurationManager();
}
}
}
return instance;
}
public String get(String key) {
return properties.getProperty(key);
}
public String get(String key, String defaultValue) {
return properties.getProperty(key, defaultValue);
}
public int getInt(String key, int defaultValue) {
String value = properties.getProperty(key);
if (value == null) return defaultValue;
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
return defaultValue;
}
}
}
// Usage
public class Application {
public static void main(String[] args) {
ConfigurationManager config = ConfigurationManager.getInstance();
String dbHost = config.get("db.host", "localhost");
int dbPort = config.getInt("db.port", 5432);
String appName = config.get("app.name", "DefaultApp");
System.out.println("App: " + appName);
System.out.println("DB: " + dbHost + ":" + dbPort);
// Same instance everywhere
ConfigurationManager sameConfig = ConfigurationManager.getInstance();
System.out.println("Same instance: " + (config == sameConfig)); // true
}
}Python Example
The same configuration manager scenario in Python, using a class-level instance with thread safety.
import threading
import json
import os
class ConfigurationManager:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
# Double-checked locking
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
if self._initialized:
return
self._initialized = True
self._config = {}
self._load_config()
def _load_config(self):
config_path = "config.json"
if os.path.exists(config_path):
with open(config_path, "r") as f:
self._config = json.load(f)
print("Configuration loaded from config.json")
else:
# Load defaults
self._config = {
"db.host": "localhost",
"db.port": 5432,
"app.name": "MyApplication",
}
print("Loaded default configuration")
def get(self, key, default=None):
return self._config.get(key, default)
def get_int(self, key, default=0):
value = self._config.get(key)
if value is None:
return default
try:
return int(value)
except (ValueError, TypeError):
return default
# Usage
if __name__ == "__main__":
config = ConfigurationManager()
db_host = config.get("db.host", "localhost")
db_port = config.get_int("db.port", 5432)
app_name = config.get("app.name", "DefaultApp")
print(f"App: {app_name}")
print(f"DB: {db_host}:{db_port}")
# Same instance everywhere
same_config = ConfigurationManager()
print(f"Same instance: {config is same_config}") # TrueWhen to Use
- Configuration management — when your application needs a single source of truth for settings loaded from files, environment variables, or remote config services.
- Connection pooling — managing a shared pool of database or HTTP connections where multiple instances would waste resources or cause connection limits to be exceeded.
- Logging — a centralized logger that all modules write to, ensuring consistent log formatting, output destinations, and thread-safe writes.
- Caching layers — an in-memory cache (like an LRU cache) that must be shared across the application to avoid duplicate data and stale entries.
Real-World Usage
- Java Runtime —
Runtime.getRuntime()returns the single Runtime instance for the JVM. - Spring Framework — Spring beans are singletons by default. The IoC container manages one instance per bean definition.
- Python logging module —
logging.getLogger(name)returns the same logger instance for a given name across the entire application. - Android SharedPreferences — the preferences instance is cached and shared across components to avoid redundant disk reads.
- Database drivers — connection managers in libraries like HikariCP and SQLAlchemy use singleton-like patterns to manage connection pools.