Singleton Pattern Interview Questions | JiQuest

add

#

Singleton Pattern

Java design pattern deep dive

Singleton Pattern in Java: 15 scenario questions with professional answers.

Learn when a Singleton is useful, how to make it thread-safe, and where it becomes risky in real systems such as logging, configuration, caches, pools, API keys, sessions, and shared resources.

15Scenarios
5Safe variants
3Anti-pattern checks
Client A Client B Client C getInstance()single access point One Objectshared safely creation guarded by JVM or locking

What makes a good Singleton answer?

Interviewers are usually testing more than syntax. They want to hear lifecycle, thread safety, testability, and whether Singleton is the right design choice.

One instancePrivate constructor plus one controlled access point.
Safe creationUse enum, initialization-on-demand holder, or careful locking.
Clear ownershipThe object should own shared, process-wide state or coordination.
Testable designPrefer dependency injection when global access would hide dependencies.
Need oneprocess instance? Use enumsimple, safest Use holderlazy and clean Need frameworklifecycle? DI beanpreferred
ApproachUse whenWatch out for
enum SingletonYou want the safest simple Singleton, protected against reflection and serialization issues.Less flexible if you need lazy construction with checked exceptions or inheritance.
Initialization-on-demand holderYou want lazy loading without explicit synchronization.Still a global dependency if overused.
Double-checked lockingYou need lazy initialization and cannot use holder or enum.The instance field must be volatile.
Spring singleton beanA framework owns lifecycle, wiring, testing, metrics, and configuration.It is singleton per application context, not necessarily per JVM cluster.

Topics

Scenario questions and answers

Each answer gives the implementation direction, the trade-off to mention, and the production concern that makes the answer stronger.

1. Configuration manager: how would you implement a Singleton for global configuration?

Use a Singleton only if configuration is truly process-wide and mostly read-only after startup. The clean Java answer is the initialization-on-demand holder idiom because it is lazy, thread-safe, and does not require explicit synchronization.

public final class ConfigManager {
    private final Properties properties = new Properties();

    private ConfigManager() {
        try (InputStream in = ConfigManager.class.getResourceAsStream("/app.properties")) {
            if (in != null) {
                properties.load(in);
            }
        } catch (IOException ex) {
            throw new IllegalStateException("Unable to load configuration", ex);
        }
    }

    private static class Holder {
        private static final ConfigManager INSTANCE = new ConfigManager();
    }

    public static ConfigManager getInstance() {
        return Holder.INSTANCE;
    }

    public String get(String key) {
        return properties.getProperty(key);
    }
}
Private constructorLazy loadingThread-safe JVM class loading

2. Logging service: what is a thread-safe Singleton approach?

For interview code, use an enum Singleton because it is concise and safe against reflection and serialization. For production logging, prefer a proven framework such as Logback or Log4j2 because file rotation, async appenders, formatting, backpressure, and error handling are already solved.

public enum AppLogger {
    INSTANCE;

    public void info(String message) {
        System.out.println(Thread.currentThread().getName() + " INFO " + message);
    }
}

// usage
AppLogger.INSTANCE.info("Order created");
Professional note A logger can be globally accessible, but business services should still receive it through dependency injection or a logging facade when possible.

3. Database connection pool: how would Singleton manage it efficiently?

A database connection pool is a strong Singleton-like candidate because creating multiple pools accidentally can exhaust database connections. In real applications, use HikariCP or the pool managed by Spring Boot. The Singleton should manage the pool object, not one raw database connection.

public final class DataSourceProvider {
    private final HikariDataSource dataSource;

    private DataSourceProvider() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(System.getenv("JDBC_URL"));
        config.setUsername(System.getenv("DB_USER"));
        config.setPassword(System.getenv("DB_PASSWORD"));
        config.setMaximumPoolSize(20);
        dataSource = new HikariDataSource(config);
    }

    private static class Holder {
        private static final DataSourceProvider INSTANCE = new DataSourceProvider();
    }

    public static DataSource getDataSource() {
        return Holder.INSTANCE.dataSource;
    }
}
Avoid Do not make a Singleton that returns the same Connection object to every thread. Connections are borrowed from a pool and closed back to the pool after use.

4. Caching system: how would you apply Singleton while keeping consistency?

Use a Singleton cache when the cache is local to one JVM and all callers should share the same in-memory state. Use ConcurrentHashMap for thread-safe access and define eviction, TTL, maximum size, and invalidation strategy. For production, prefer Caffeine.

public enum ProductCache {
    INSTANCE;

    private final ConcurrentMap<String, Product> cache = new ConcurrentHashMap<>();

    public Product get(String id, Supplier<Product> loader) {
        return cache.computeIfAbsent(id, key -> loader.get());
    }

    public void invalidate(String id) {
        cache.remove(id);
    }
}
Shared stateConcurrentHashMapInvalidation policy

5. Shared resource across threads: what techniques make Singleton thread-safe?

The main techniques are eager initialization, enum Singleton, synchronized accessor, double-checked locking with volatile, and initialization-on-demand holder. In modern Java interviews, the best default answers are enum or holder because they are simpler and less error-prone.

public final class SharedResourceManager {
    private static volatile SharedResourceManager instance;

    private SharedResourceManager() {}

    public static SharedResourceManager getInstance() {
        if (instance == null) {
            synchronized (SharedResourceManager.class) {
                if (instance == null) {
                    instance = new SharedResourceManager();
                }
            }
        }
        return instance;
    }
}
Key detail In double-checked locking, volatile is required so other threads do not see a partially constructed object.

6. Network connection manager: how do you ensure only one manager is created?

Make the manager Singleton, but keep individual network connections as managed resources inside it. The Singleton coordinates connection creation, pooling, timeouts, retries, and shutdown. It should expose operations, not mutable internals.

public final class NetworkManager {
    private final ExecutorService ioPool = Executors.newFixedThreadPool(8);
    private final HttpClient client = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(3))
        .build();

    private NetworkManager() {}

    private static class Holder {
        private static final NetworkManager INSTANCE = new NetworkManager();
    }

    public static NetworkManager getInstance() {
        return Holder.INSTANCE;
    }

    public CompletableFuture<HttpResponse<String>> get(URI uri) {
        HttpRequest request = HttpRequest.newBuilder(uri).GET().build();
        return client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
    }
}

7. Settings manager: how can it be globally accessible and consistent?

Use a Singleton settings manager if settings are shared and do not depend on the current user or request. Keep settings immutable after loading, or update them through an atomic snapshot so readers never see half-updated data.

public final class SettingsManager {
    private final AtomicReference<Map<String, String>> settings =
        new AtomicReference<>(Map.of());

    private SettingsManager() {}

    private static class Holder {
        private static final SettingsManager INSTANCE = new SettingsManager();
    }

    public static SettingsManager getInstance() {
        return Holder.INSTANCE;
    }

    public String get(String key) {
        return settings.get().get(key);
    }

    public void reload(Map<String, String> newSettings) {
        settings.set(Map.copyOf(newSettings));
    }
}

8. Resource pool such as a thread pool: what is a common Singleton approach?

Create one provider that owns the pool and exposes submit/shutdown methods. The pool must be bounded and closed during application shutdown. In Spring, this is better as a managed bean because the framework can shut it down cleanly.

public enum WorkerPool {
    INSTANCE;

    private final ExecutorService executor =
        new ThreadPoolExecutor(4, 8, 60, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(500),
            new ThreadPoolExecutor.CallerRunsPolicy());

    public Future<?> submit(Runnable task) {
        return executor.submit(task);
    }

    public void shutdown() {
        executor.shutdown();
    }
}
Bounded queueShutdown hookBackpressure

9. Shared cache initialized only once: how can Singleton help?

Use lazy initialization so the cache loads only when first needed. The holder idiom is ideal because the JVM initializes the nested class once, safely, on first access. If initialization is expensive, load asynchronously and expose readiness status.

public final class ReferenceDataCache {
    private final Map<String, String> values;

    private ReferenceDataCache() {
        values = loadReferenceData();
    }

    private static class Holder {
        private static final ReferenceDataCache INSTANCE = new ReferenceDataCache();
    }

    public static ReferenceDataCache getInstance() {
        return Holder.INSTANCE;
    }

    public String lookup(String code) {
        return values.get(code);
    }
}

10. Global logging service: how do you ensure only one instance exists?

Use a private constructor and a single static instance, but also make the logging operation thread-safe. If writing to files, serialize writes through a queue or use a mature logging framework. A Singleton controls instance count; it does not automatically make every method safe.

public final class SimpleLogger {
    private static final SimpleLogger INSTANCE = new SimpleLogger();
    private final BlockingQueue<String> queue = new LinkedBlockingQueue<>();

    private SimpleLogger() {
        Thread writer = new Thread(this::writeLoop, "log-writer");
        writer.setDaemon(true);
        writer.start();
    }

    public static SimpleLogger getInstance() {
        return INSTANCE;
    }

    public void log(String message) {
        queue.offer(Instant.now() + " " + message);
    }

    private void writeLoop() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                System.out.println(queue.take());
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

11. API key manager: how would you manage keys across the application?

A Singleton can centralize API key lookup and rotation, but secrets should not be hardcoded or logged. Load keys from a secret manager, environment variable, or encrypted configuration. Return the minimum data needed and support refresh without restarting the application.

public final class ApiKeyManager {
    private final AtomicReference<String> currentKey = new AtomicReference<>();

    private ApiKeyManager() {
        currentKey.set(loadFromSecretStore());
    }

    private static class Holder {
        private static final ApiKeyManager INSTANCE = new ApiKeyManager();
    }

    public static ApiKeyManager getInstance() {
        return Holder.INSTANCE;
    }

    public String currentKey() {
        return currentKey.get();
    }

    public void rotate() {
        currentKey.set(loadFromSecretStore());
    }
}
Security note Do not expose API keys through debug endpoints, exception messages, or normal logs.

12. System configuration manager: how can you make it thread-safe?

Use immutable configuration snapshots. Readers get a stable view, while reload swaps the whole config atomically. This avoids locking on every read and prevents partially updated settings.

public record SystemConfig(String region, int timeoutMs, boolean featureEnabled) {}

public final class SystemConfigManager {
    private final AtomicReference<SystemConfig> config =
        new AtomicReference<>(new SystemConfig("us-east", 3000, false));

    private SystemConfigManager() {}

    private static class Holder {
        private static final SystemConfigManager INSTANCE = new SystemConfigManager();
    }

    public static SystemConfigManager getInstance() {
        return Holder.INSTANCE;
    }

    public SystemConfig current() {
        return config.get();
    }

    public void reload(SystemConfig nextConfig) {
        config.set(nextConfig);
    }
}

13. Logging framework writing to one file: how do you ensure one instance is used?

The Singleton should own one writer pipeline and one file handle. Multiple threads should enqueue log events, while one background writer drains the queue. Add flushing, rotation, backpressure, and shutdown behavior.

Thread 1 Thread 2 Thread 3 Singletonlog queue Writerone thread Logfile

This design prevents several application threads from writing to the same file handle at the same time.

14. User sessions in a web application: should this be a Singleton?

Be careful: a Singleton session manager is acceptable, but user session data itself must not be stored as fields on the Singleton. In a web app, sessions are per user and often distributed across servers. Store session data in the servlet container, Redis, database, or a dedicated session store.

public final class SessionRegistry {
    private final ConcurrentMap<String, SessionInfo> sessions = new ConcurrentHashMap<>();

    private SessionRegistry() {}

    private static class Holder {
        private static final SessionRegistry INSTANCE = new SessionRegistry();
    }

    public static SessionRegistry getInstance() {
        return Holder.INSTANCE;
    }

    public void register(String sessionId, SessionInfo info) {
        sessions.put(sessionId, info);
    }

    public void remove(String sessionId) {
        sessions.remove(sessionId);
    }
}
Interview warning Never put currentUser as a field in a Singleton. That leaks users across requests and threads.

15. Shared printer resource: how would Singleton coordinate access?

Use the Singleton as a coordinator with a queue and one worker, so print jobs are processed in order. This avoids multiple threads talking to the printer at the same time and gives you a place for retries, cancellation, audit logs, and status checks.

public enum PrinterSpooler {
    INSTANCE;

    private final BlockingQueue<PrintJob> jobs = new LinkedBlockingQueue<>();

    PrinterSpooler() {
        Thread worker = new Thread(this::processJobs, "printer-spooler");
        worker.setDaemon(true);
        worker.start();
    }

    public void submit(PrintJob job) {
        jobs.offer(job);
    }

    private void processJobs() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                PrintJob job = jobs.take();
                job.print();
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }
}
Single coordinatorQueueOrdered accessRetryable jobs
No comments
Leave a Comment