StackPractices
beginner By Mathias Paulenko

Singleton Pattern: Java, Python & TypeScript Examples

Ensure a class has only one instance and provide global access to it — the creational design pattern for controlled object creation, with production-ready implementations.

Topics: design

Overview

Your app just opened a second database connection pool and now every query competes for half the connections. That’s the problem Singleton exists to solve: some resources must exist exactly once — a connection pool, a config reader, a logger — and the Singleton Pattern guarantees that single instance plus a controlled way to reach it.

The pattern comes from the Gang of Four (Design Patterns, 1994), and it’s the most widely used — and most abused — creational pattern. This page covers the implementations that actually survive production: lazy and eager init, double-checked locking with volatile, the Bill Pugh holder, Java’s enum singleton, module-level singletons in Python and Node, registries, and the dependency-injection alternative you’ll want for anything testable.

When to Use

Use Singleton when all of these hold:

  • Exactly one instance must exist — a connection pool, an in-memory cache, a config manager, a logger factory.
  • A second instance breaks things — a second pool doubles the connections hitting your database.
  • You want lazy initialization to defer expensive setup until first use.

Do not use it when:

  • The object is mutable business state — that’s global state with extra steps. Pass it as a parameter instead.
  • You need per-user or per-request data — a singleton holding request state is a memory leak.
  • Testability matters more than convenience — prefer dependency injection, which gives you the same single instance with an explicit contract.

Solution

Java — the implementations that matter

Start with the enum singleton. It’s three lines, it’s thread-safe by JVM guarantee, and it’s the only variant immune to serialization and reflection attacks (Joshua Bloch’s recommendation in Effective Java, item 3):

public enum Configuration {
    INSTANCE;

    private final Map<String, String> properties = loadProperties();

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

    private Map<String, String> loadProperties() {
        return Map.of("env", System.getenv("APP_ENV"));
    }
}

// Usage — the same instance everywhere, impossible to duplicate
String env = Configuration.INSTANCE.get("env");

If you can’t use an enum (you need inheritance), the Bill Pugh holder idiom gives lazy init with zero synchronization cost — the JVM only loads the inner class on first access:

public class ConnectionPool {
    private ConnectionPool() {
        // expensive setup: open pool, bind sockets
    }

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

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

And if you need conditional or configurable construction, double-checked locking works — but only with volatile, otherwise a second thread can read a half-constructed instance:

public class CacheClient {
    private static volatile CacheClient instance;

    private CacheClient() {}

    public static CacheClient getInstance() {
        if (instance == null) {                     // fast path, no lock
            synchronized (CacheClient.class) {
                if (instance == null) {             // second check inside the lock
                    instance = new CacheClient();
                }
            }
        }
        return instance;
    }
}

The naive synchronized getInstance() you see in older tutorials is correct but slower — it locks on every call, not just the first.

Python — prefer the module, not __new__

The classic __new__ singleton has two traps nobody warns you about: __init__ runs again on every call (re-initializing your state), and the check-then-act inside __new__ is not atomic — the GIL doesn’t save you here, two threads can still race between the None check and the assignment. If you insist on __new__, guard it with a lock:

import threading

class ConnectionPool:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if self._initialized:
            return                          # don't re-run setup
        self._pool = self._create_pool()
        self._initialized = True

The cleaner Python idiom is the module itself: modules are imported once and cached in sys.modules, so a module-level instance is a singleton for free — thread-safe, lazy on first import, and the recommended approach:

# connection_pool.py
import psycopg2.pool

pool = psycopg2.pool.ThreadedConnectionPool(
    minconn=2, maxconn=10,
    dsn="postgresql://user:pass@localhost/db",
)

# Every importer gets the same object:
from connection_pool import pool

TypeScript — private constructors and registries

A private constructor blocks new, and a static accessor controls the single instance. For anything beyond the simplest case, a named registry is more useful — it maps keys to instances, so you get “one per name” (a Multiton actually) plus a reset hook for tests:

class Registry {
  private static instances = new Map<string, unknown>();

  static get<T>(key: string, factory: () => T): T {
    if (!Registry.instances.has(key)) {
      Registry.instances.set(key, factory());
    }
    return Registry.instances.get(key) as T;
  }

  static reset(key: string): void {
    Registry.instances.delete(key);
  }
}

const pool = Registry.get('db-pool', () => createPool(config));
Registry.reset('db-pool'); // in tests, between cases

Node.js also gives you singletons for free the same way Python does: require/import caches modules, so an exported instance is shared by every importer.

Diagram

classDiagram diagram: class Singleton {

Explanation

Every Singleton implementation answers the same three questions:

  • Who blocks new? A private constructor (Java/TS), an enum, or the language’s module system (Python/Node).
  • When is the instance created? Eagerly at load, or lazily on first access — lazy saves startup cost but introduces the thread-safety problem.
  • Who enforces single-instance? The class itself, a registry, or the DI container.

On thread safety: the race is always in lazy init. Two threads hit if (instance == null) at once, both construct, and the second overwrites the first. synchronized fixes it naively; volatile + double-checked locking keeps the fast path lock-free; the holder idiom offloads it to class-loading guarantees. In Java there’s a subtler issue — without volatile, a thread can see a non-null reference to an object whose constructor hasn’t finished writing fields, because the JVM may reorder the “allocate → construct → assign” steps.

Variants

VariantLazyThread-safeTestableWhen it wins
Eager staticNoYesPoorCheap init, always needed
Synchronized accessorYesYesPoorSimple, low-traffic
Double-checked lockingYesYes*PoorHot path, Java/C# — needs volatile
Bill Pugh holderYesYesPoorJava lazy init, no lock cost
Enum singletonNoYesPoorJava — survives serialization and reflection
Module-levelYesYesPoorPython, Node — idiomatic, zero machinery
Registry / MultitonYesYesGoodNamed instances, test reset hooks
DI containerYesYesExcellentAnything you need to mock

*Only with volatile; without it the check-then-act is still broken.

Singleton in tests — the part tutorials skip

A getInstance() singleton makes tests order-dependent: whatever one test mutates leaks into the next. Three escapes:

// 1. Registry with reset — clear between tests
afterEach(() => Registry.reset('db-pool'));

// 2. DI container — bind a mock for the test run
container.bind(IPool).to(MockPool).inSingletonScope();
const service = container.get(OrderService);

// 3. Constructor injection — pass the instance explicitly
const service = new OrderService(pool); // pool is the singleton in prod, a fake in tests

The honest answer is option 3. If a piece of code might ever need a second implementation in tests, inject the dependency and keep the singleton only as the production binding.

Serialization, reflection, and classloaders

Three ways Singleton breaks silently, all real:

  • Serialization (Java): deserializing creates a new instance regardless of the private constructor. Fix it with readResolve():
    protected Object readResolve() {
        return getInstance();
    }
    Or use an enum singleton, which handles it automatically.
  • Reflection: setAccessible(true) can call your private constructor. Enum singletons are immune — the JVM guarantees a single constant.
  • Multiple classloaders: two classloaders loading the same class produce two “singletons” — common in app servers and plugins. Python and Node do the same with duplicate package copies. If you’re seeing two instances, look there first — System.identityHashCode() or id() will confirm it.

Alternatives

OptionWhat it gives youWhat it costs
DI container (Spring, ASP.NET, Nest)Singleton lifecycle + testability + explicit contractsFramework dependency
Module system (Python, Node)Idiomatic singleton for freeHard to swap in tests
MonostateShared state, multiple instancesSame global-state risks
Static classSimplest possibleNo polymorphism, no mocking, no injection

Singleton vs static class deserves the extra line: a singleton is an object — it can implement an interface, be passed as a parameter, and be replaced in tests. A static class can’t do any of that.

What Works

  • Keep the singleton’s state immutable or read-only after init — mutable global state is where the real bugs live.
  • For Java: reach for enum or Bill Pugh first; they’re correct without you proving it.
  • For Python/Node: use the module system; don’t reinvent getInstance().
  • Put the “how do I test this” story in writing — a registry reset() or a DI binding — before the first teammate hits it.
  • Document what the singleton owns (connections, cache) and what cleanup looks like at shutdown.

Common Mistakes

  • Assuming __new__ is thread-safe because of the GIL — it isn’t; use a lock or module-level init.
  • Forgetting __init__ re-runs on every Singleton() call in Python and re-initializes the state.
  • Double-checked locking without volatile — looks right, races in production.
  • A singleton holding per-request or per-user data — that’s a memory leak wearing a pattern’s clothes.
  • Business-logic services as singletons — a UserService singleton is a coupling bomb; inject it instead.
  • Skipping the shutdown path — pools and executors leak if nobody calls close() on exit.

Further Reading

Frequently Asked Questions

Is the Singleton Pattern an anti-pattern?

Singleton itself isn't the problem — global mutable state is. A singleton for an immutable config or a connection pool is legitimate infrastructure. A singleton for business state makes tests order-dependent and couples everything to everything.

How do I make a Python singleton thread-safe?

Don't rely on the GIL — the check-then-act inside __new__ can still race. Guard __new__ with threading.Lock, or better, use a module-level instance: Python modules are imported once and cached, so they're thread-safe singletons by construction.

What's the best singleton implementation in Java?

The enum singleton. It's thread-safe by JVM guarantee, lazy at class init, and immune to serialization and reflection attacks — no other variant is. If you need lazy init with a real class, use the Bill Pugh holder idiom.

How do I test code that depends on a singleton?

Best answer: don't call getInstance() inside business logic — inject the dependency through the constructor and bind the singleton in the DI container. If you're stuck with getInstance(), use a registry variant with a reset() hook so each test gets a fresh instance.

Singleton vs static class — when does it matter?

A singleton is an object: it can implement interfaces, be injected, and be mocked. A static class is just a namespace for functions. If you might need polymorphism or test doubles, use a singleton; if it's pure stateless utility, a static class is simpler.

Does double-checked locking still need volatile?

Yes, in Java. Without volatile a thread can observe a non-null reference to a partially constructed object — the JVM is allowed to publish the reference before the constructor finishes. It's the most commonly broken implementation detail.

Can a singleton be serialized?

Naively, yes — and deserialization will create a second instance. Implement readResolve() to return the existing instance, or use an enum singleton, which handles it automatically.

What's the difference between Singleton and Multiton?

A Singleton enforces exactly one instance; a Multiton enforces one instance per key — a map of named singletons. Registries are Multitons in practice.