Skip to content
Sanfor2004
← All articles
Design PatternsDesign PatternsCreational PatternsPythonC++Software Engineering

Singleton (Creational Pattern)

By Ahmed (Sanfor) · 11 Sept 2026 · 5 min read
One central fire-alarm panel receives reports from across a building.

Start with the design patterns overview · Part 05 of 23 · Creational patterns

Provide one controlled instance and a global access point—while understanding the coupling it creates. This article follows the example in my 23 Design Patterns repository, connecting the problem, participating classes, Python and C++20 implementations, and the trade-offs that decide whether to use it.

The Problem

Two independently created metrics counters split a process-wide total.

Naive Solution

Metrics first;
Metrics second; // separate counters; assumes a public constructor

Why It Becomes a Problem

Making the constructor public gives each caller its own counter; the intended shared total is no longer shared.

The Idea

Hide construction, delete copying, and return a function-local static instance.

Reading the illustration

A building has one official fire-alarm control panel. Everyone may report to it, but adding a second independent panel would split the truth.

In the repository example, the same design idea addresses this software problem: Two independently created metrics counters split a process-wide total.

Structure

Diagram · Python source · C++20 source

Singleton sketch map: Client A + B leads through Metrics::instance() to one Metrics.
Singleton: trace the example from caller through the pattern boundary to its collaborator or result. The arrows show flow, not ownership. Open the full-size diagram.
Client A + B  -->  Metrics::instance()  -->  one Metrics

Participants

Metrics controls its lifetime (The interval during which an object exists and may be used according to its rules) and stores the count. instance returns a non-owning reference; callers must never delete it.

Canonical roles in this example:

  • instance — A particular object of a type. Here: Metrics::instance().
  • global state — Data reachable broadly across a program whose changes can affect distant code. Here: Metrics::requests_.
  • thread-safe initialization — Initialization protected against concurrent construction; it does not make later operations thread-safe. Here: static Metrics metrics.

Python Example

The complete Python source is shown first.

class Metrics:
    def __init__(self):
        self.requests = 0

    def record(self):
        self.requests += 1


# One shared instance under normal imports of this module.
# This expresses shared access, not a ban on creating other Metrics objects.
metrics = Metrics()


def main():
    first = metrics
    second = metrics
    first.record()
    second.record()
    print("Same instance:", first is second)
    print("Requests:", metrics.requests)


if __name__ == "__main__":
    main()

Python Output

Same instance: True
Requests: 2

Code Walkthrough

Metrics controls its lifetime and stores the count. instance returns a non-owning reference; callers must never delete it.

Start at the final call in the Python example. Follow the middle role in the diagram and compare how the C++20 version handles the same responsibility.

C++20 Example

#include <iostream>

class Metrics {
    int requests_ = 0;
    Metrics() = default;
public:
    Metrics(const Metrics&) = delete;
    Metrics& operator=(const Metrics&) = delete;
    static Metrics& instance() {
        static Metrics metrics;
        return metrics;
    }
    void record() { ++requests_; }
    int requests() const { return requests_; }
};
int main() {
    auto& first = Metrics::instance();
    auto& second = Metrics::instance();
    first.record();
    second.record();
    std::cout << "Same instance: " << std::boolalpha << (&first == &second) << '\n';
    std::cout << "Requests: " << first.requests() << '\n';
}

C++20 Output

Same instance: true
Requests: 2

When to Use

Consider it only when one instance really is a process-level invariant and its lifetime is appropriate.

Use cases

A tiny single-threaded diagnostic counter demonstrates the mechanism, not a recommendation for production metrics architecture.

When NOT to Use

Avoid it for convenient access to ordinary dependencies. Pass a Metrics reference explicitly when tests need isolation.

Advantages

There is one well-defined initialization point and callers reach the same object.

Trade-offs

Global access hides dependencies and contaminates tests. Local-static initialization is thread-safe, but record is not; concurrent calls need synchronization. Shutdown order can also matter.

Abstract Factory · Facade

Common Confusion

One object managed by dependency injection (Supplying a dependency from outside instead of choosing or constructing it inside the consumer) is not necessarily a Singleton: uniqueness need not be enforced by the type.

Terms to Remember

  • Singleton — Restrict a type to one accessible instance, accepting the cost of shared global state.
  • instance — A particular object of a type. Example: Metrics::instance().
  • global state — Data reachable broadly across a program whose changes can affect distant code. Example: Metrics::requests_.
  • thread-safe initialization — Initialization protected against concurrent construction; it does not make later operations thread-safe. Example: static Metrics metrics.

Interview Vocabulary

  • dependency injection — Supplying a dependency from outside instead of choosing or constructing it inside the consumer.
  • testability — How readily behavior can be isolated, exercised, and checked.
  • lifetime — The interval during which an object exists and may be used according to its rules.

Interview Question

Does thread-safe initialization make requests_ thread-safe? Identify the separate operations involved.

Mini Challenge

Refactor the example to inject a Metrics-like counter into two jobs, then test two isolated counters.

Compare the two versions

Python uses one module-level instance, a common alternative to a strict Singleton Class. It does not prevent callers from constructing Metrics. C++ makes its constructor private and deletes copying. Shared mutable State complicates isolation in both; prefer passing a Dependency explicitly. Neither counter is thread-safe. The two examples express the same pattern responsibility; compare their setup and output before changing an input.

Check yourself

  1. How could one test leave counter State that affects the next test?
  2. When would the naive solution on this page be easier to maintain? Give a concrete example.
  3. Change one input in the Python example. Predict the output and explain which responsibility handles the change.

Run and explore the example

The Python code comes from python/main.py, with expected output. The C++20 code comes from creational/singleton/cpp/main.cpp. Follow the repository’s C++20 build instructions to compile it and compare the result with expected.txt. The output demonstrates this example’s behavior; it does not cover every input or the challenge above.

The source example and adapted explanation are © 2026 Sanfor2004, provided under the MIT license. The sketchbook cover is an illustration preserved from this site’s original pattern lessons.

Continue the series

Ahmed (Sanfor)Backend and automation engineer in Alexandria. I build the systems businesses run on, and paint between deploys.

Keep reading

Design PatternsAbstract Factory (Creational Pattern)5 minDesign PatternsAdapter (Structural Pattern)4 minDesign PatternsBridge (Structural Pattern)5 min

Have manual work eating your week?

Book a free call