Design Patterns «Prev Next»

Lesson 9Singleton: known uses
ObjectiveExplore Real World Examples of the Singleton Design Pattern.

Known Uses of the Singleton Design Pattern

Internal critique (Use Case 2)

  • Strength: The page names classic Singleton motivations (single shared instance, global access) and gives a practical example (configuration).
  • Gaps: It does not clearly separate “singleton as a language/runtime guarantee” from “single instance by deployment convention” (for example, Node’s module cache, clustered processes, containers).
  • Risk: Singleton guidance is incomplete without addressing testability, hidden coupling, lifecycle/teardown, and concurrency (thread safety, async initialization).
  • Modernization need: Real-world usage today often appears as a process-wide service (logger, metrics registry, feature-flag client) or as a shared resource manager (connection pool, cache), frequently instantiated by a DI container rather than a handwritten “getInstance().”

What “Singleton” really means in production code

In practice, “Singleton” usually means exactly one instance per application boundary. The boundary matters: a single JVM, a single Node.js process, a single desktop app process, or a single service instance in a container. If your deployment runs multiple processes (Node cluster, Kubernetes replicas, Windows services across multiple hosts), you do not automatically get “one instance globally”—you get one instance per process.

Use Singleton when you truly need a single coordinator for a shared resource (serialization, caching, or shared configuration). Avoid it when it becomes a disguised global variable that spreads hidden dependencies.

Real-world examples you will encounter

1) Logging broker

A logger is a classic Singleton candidate because it brokers access to an append-only destination (file, stdout, syslog, remote log sink). The goal is consistent formatting, consistent metadata (request id, user id), and serialized writes to avoid interleaving. In many ecosystems, the “singleton” is owned by the logging framework (for example, a global logger registry).

2) Configuration and feature flags

Applications frequently load configuration once (environment variables, config files, secret managers) and provide a stable read interface. The same pattern appears in feature-flag clients (LaunchDarkly-style SDKs, internal toggles): one client instance, shared cache, periodic refresh, and a single place to manage credentials and lifecycle.

3) Metrics registry / telemetry

Telemetry libraries commonly expose a process-wide registry for counters, gauges, histograms, and tracing providers. One registry prevents duplicated metrics names and ensures consistent labeling across the codebase.

4) Connection pool or shared client

Database clients, HTTP clients, and connection pools are often instantiated once and reused. This is less about “one instance because pattern” and more about efficient resource reuse, throttling, and consistent retry/backoff policies.

5) In-process cache

A shared cache (LRU cache, memoization store, compiled-regex cache) is commonly centralized to avoid duplicate memory usage and to coordinate eviction strategy.


NodeJS Design Patterns

Singleton in Node.js

In Node.js, the most common “Singleton” is simply a module export. The first import/require constructs the object; subsequent imports receive the cached instance (per process). This is usually preferable to a manual getInstance() because it aligns with the platform’s module system.

Example: configuration manager (module singleton)

// configManager.js (CommonJS)
class ConfigManager {
  constructor() {
    // Example: initialize once from environment, file, secret manager, etc.
    this.config = Object.freeze({
      appName: process.env.APP_NAME || "gofpattern",
      logLevel: process.env.LOG_LEVEL || "info"
    });
  }

  get(key) {
    return this.config[key];
  }
}

module.exports = new ConfigManager();
// usage.js
const config = require("./configManager");
console.log(config.get("logLevel"));

Notes for modern Node deployments

  • Cluster/replicas: each worker/process gets its own instance; coordinate globally via Redis, DB, or a service.
  • Testing: module-singletons can leak state across tests; expose a factory or a reset hook in test builds.
  • Mutable state: prefer immutable config; if you must mutate, document lifecycle and thread/async safety.

Modern Singleton implementations in other languages

Java: enum singleton (simple, serialization-safe)

public enum AppConfig {
    INSTANCE;

    private final java.util.Properties props = new java.util.Properties();

    AppConfig() {
      // Load once (file, classpath resource, env, etc.)
      props.setProperty("logLevel", System.getProperty("logLevel", "INFO"));
    }

    public String get(String key) {
      return props.getProperty(key);
    }
}

C++: function-local static (thread-safe since C++11)

#include <string>

class Logger {
public:
  static Logger& instance() {
    static Logger inst; // initialized once, thread-safe (C++11+)
    return inst;
  }

  void log(const std::string& msg) {
    // write to sink
  }

private:
  Logger() = default;
  Logger(const Logger&) = delete;
  Logger& operator=(const Logger&) = delete;
};

In both cases, treat Singleton as a lifecycle-managed service: define initialization rules, provide a clean shutdown path when required, and avoid hiding important dependencies.

Applicability of the Singleton Design Pattern

Use Singleton when:

  1. Exactly one instance is required within a clearly defined boundary (process/JVM/runtime) and clients need a stable access point.
  2. A single coordinator must broker access to a resource (logging sink, metrics registry, shared cache, pool).
  3. Centralized policy is important (retry strategy, rate limiting, feature-flag evaluation rules) and duplication would cause inconsistent behavior.

Prefer alternatives when:

  • You mainly want global access—use dependency injection (constructor injection) and pass dependencies explicitly.
  • You need one instance per request/user/session—use a scoped service, not a singleton.
  • You need “one instance across multiple machines”—use a distributed coordination mechanism (database locks, Redis, Zookeeper/etcd, leader election).

Classic references and a modern caution

Many classic libraries contain objects that behave like singletons: global registries, session managers, GUI toolkits, and system services. Some historical examples referenced in older literature may point to internal or non-public APIs; treat those as illustrative rather than prescriptive.

Once you recognize the intent—“single shared instance with controlled access”—you will see it frequently. The key is to apply it deliberately: keep state minimal, document lifecycle, and avoid turning Singleton into a convenient hiding place for unrelated responsibilities.


SEMrush Software 9 SEMrush Banner 9