Singleton Pattern

Published on: October 06, 2025

Tags: #singleton #design-patterns


The Classic Singleton Pattern

classDiagram
    %% Adding a stereotype to make the pattern role explicit
    class Singleton {
        <>
        -_instance: Singleton$
        -Singleton()
        +getInstance(): Singleton$
    }

    %% The note remains the same and is still very helpful
    note for Singleton "getInstance() logic:\nif _instance is null:\n  _instance = new Singleton()\nreturn _instance"

    class ClientA {
        -singleton_instance: Singleton
        +do_work()
    }
    class ClientB {
        -singleton_instance: Singleton
        +do_something_else()
    }

    ClientA ..> Singleton : uses
    ClientB ..> Singleton : uses

The Anti-Pattern Problem: Global Shared State

graph TD
    %% All relationships are now grouped inside the subgraph
    subgraph Application
        direction TB
        ModuleA -->|Reads/Writes to| Singleton["Singleton (Global State)"];
        ModuleB -->|Reads/Writes to| Singleton;
        ModuleC -->|Reads from| Singleton;

        ModuleA -.-> |Invisible Coupling| ModuleB;
        ModuleB -.-> |Invisible Coupling| ModuleC;
    end

    %% Added styling for the modules to match the image's purple
    style ModuleA fill:#e6e6fa,stroke:#333,stroke-width:2px
    style ModuleB fill:#e6e6fa,stroke:#333,stroke-width:2px
    style ModuleC fill:#e6e6fa,stroke:#333,stroke-width:2px

    %% Kept the original styling for the Singleton
    style Singleton fill:#f9f,stroke:#333,stroke-width:2px

The Multi-threading Race Condition

sequenceDiagram
    participant ThreadA
    participant ThreadB
    participant SingletonClass

    par
        ThreadA->>SingletonClass: getInstance()
        activate SingletonClass
        note over ThreadA, SingletonClass: Checks if instance exists (it's null)
    and
        ThreadB->>SingletonClass: getInstance()
        activate SingletonClass
        note over ThreadB, SingletonClass: Checks if instance exists (it's null)
    end

    note right of SingletonClass: Both threads see a null instance before either can create one.

    par
        ThreadA->>SingletonClass: Creates new Instance A
        SingletonClass-->>ThreadA: Returns Instance A
        deactivate SingletonClass
    and
        ThreadB->>SingletonClass: Creates new Instance B
        SingletonClass-->>ThreadB: Returns Instance B
        deactivate SingletonClass
    end

    note right of SingletonClass: Problem! Two instances are created.

The "Pythonic" Singleton: A Module

graph TD
    subgraph YourApp
        direction TB
        A[main_app.py] -->|import config| C["config.py (Module Singleton)"];
        B[some_other_module.py] -->|import config| C;
    end

    style C fill:#ccf,stroke:#333,stroke-width:2px

The Real Reason: Controlled Instantiation (Lazy Loading)

sequenceDiagram
    participant Client
    participant predict_function
    participant ModelLoader

    Client->>predict_function: Make prediction
    activate predict_function
        predict_function->>ModelLoader: Get Instance
        activate ModelLoader
        note over ModelLoader: First call: instance does not exist.
        ModelLoader->>ModelLoader: __init__() - Loading large model...
        ModelLoader-->>predict_function: Return new instance
        deactivate ModelLoader
        predict_function->>ModelLoader: instance.predict()
    predict_function-->>Client: Return prediction
    deactivate predict_function

    Client->>predict_function: Make another prediction
    activate predict_function
        predict_function->>ModelLoader: Get Instance
        activate ModelLoader
        note over ModelLoader: Instance already exists, return it directly.
        ModelLoader-->>predict_function: Return existing instance
        deactivate ModelLoader
        predict_function->>ModelLoader: instance.predict()
    predict_function-->>Client: Return prediction
    deactivate predict_function

Related Pattern: The Object Pool

sequenceDiagram
    participant Client
    participant ObjectPool
    participant DB_Connection

    Client->>ObjectPool: acquireConnection()
    activate ObjectPool
    alt Pool has an available connection
        ObjectPool-->>Client: return existing_connection
    else Pool is empty and not at max capacity
        ObjectPool->>DB_Connection: new()
        activate DB_Connection
        DB_Connection-->>ObjectPool: new_connection
        deactivate DB_Connection
        ObjectPool-->>Client: return new_connection
    end
    deactivate ObjectPool

    Note over Client: Client uses the connection...

    Client->>ObjectPool: releaseConnection(connection)
    activate ObjectPool
    Note over ObjectPool: Connection is returned to the pool for reuse.
    deactivate ObjectPool

Share this post

Share on X  •  Share on LinkedIn  •  Share via Email