cconnected.
Understand the pattern. Find your own way.Find your starting point
Your learning feed/Idea 190 of 218
Ideas this builds on
Low-level design190 / 218

Singleton and object lifetime

Picture

A configuration value or connection pool may be shared. That is a lifetime decision, not proof every caller should fetch a global object.

Predict

Does one singleton in each of three server processes create one shared distributed instance?

Walk through the small example

No: there are three process-local instances. Distributed uniqueness requires coordination beyond a class-level variable.

Mental model and complete Python solution

Singleton restricts ordinary construction to a shared instance, but often introduces global access and difficult test isolation. ProcessSettings below returns the same object and initializes it while holding a lock. Application then demonstrates an alternative: a composition root constructs one immutable configuration and injects it into services. This preserves shared lifetime without enforcing global uniqueness. The contrast matters: the first controls construction globally; the second makes ownership explicit.

What the function promises: Ordinary ProcessSettings() calls share one initialized object in this Python interpreter. Subclassing is rejected. This is an application convention, not protection against deliberate constructor bypass, copying, serialization, or separate interpreters. Each Application instead owns a frozen Config and shares it with its own services; different Application objects remain isolated. No external resources require shutdown in this example.

from dataclasses import dataclass
from threading import Lock

class ProcessSettings:
    _instance = None
    _lock = Lock()

    def __init_subclass__(cls, **kwargs):
        raise TypeError("This singleton is not extensible")

    def __new__(cls):
        with cls._lock:
            if cls._instance is None:
                instance = super().__new__(cls)
                instance._region = "default"
                cls._instance = instance
            return cls._instance

    @property
    def region(self):
        return self._region

assert ProcessSettings() is ProcessSettings()
assert ProcessSettings().region == "default"

@dataclass(frozen=True)
class Config:
    region: str

class Service:
    def __init__(self, config):
        self.config = config

class Application:
    def __init__(self, region):
        self.config = Config(region)
        self.reader = Service(self.config)
        self.writer = Service(self.config)

a, b = Application("test-a"), Application("test-b")
assert a.reader.config is a.writer.config
assert a.config is not b.config
assert b.reader.config.region == "test-b"

Work and storage: O(1) shared references. Real shared helpers such as pools have their own resource and shutdown costs.

When to recognize it: A language-level singleton is usually one per process, not one across every server. Hidden globals also make tests and replacement harder.

Check before applying it: Shared lifetime is not distributed uniqueness. Avoid hiding important dependencies behind globals.

Change one rule

We need one job scheduler leader across many machines. Does hiding its constructor solve that?

See what changes

No. Use a shared coordination protocol or service with leases/fencing or consensus-backed leadership as appropriate. Process-local object lifetime is a different problem.

Connect

Make failure and retries part of the design: A local lock coordinates one object, not a whole fleet of servers.

Monolith, services, and ownership: Deployment ownership is a different lifetime and coordination boundary.