Skip to content
Code Kata
Go back

Kata: Singleton Pattern

Suggest edit

Table of contents

Open Table of contents

Problem Statement

Implement a Config singleton that:

  1. Can only be instantiated once
  2. Provides a global getInstance() method
  3. Supports get(key) and set(key, value) for configuration values
  4. Is lazily initialized (created on first access)

Then discuss: When should you avoid singletons?

Concepts

The singleton pattern ensures a class has only one instance and provides a global access point to it. It’s one of the simplest patterns but also one of the most controversial.

Common uses: Configuration, logging, connection pools, caches.

Problems: Hidden dependencies, global state, difficult to test, tight coupling.

TypeScript/Python: ES modules and Python modules are natural singletons — a module is only loaded once. See discussion below.

Boilerplate

TypeScript

// singleton.ts
class Config {
  private static instance: Config | null = null;
  private data: Map<string, unknown> = new Map();

  private constructor() {}

  static getInstance(): Config {
    // TODO
    throw new Error("Not implemented");
  }

  get<T>(key: string): T | undefined { /* TODO */ return undefined; }
  set(key: string, value: unknown): void { /* TODO */ }
}

Hints

Hint 1: Lazy initialization

In getInstance(), check if instance is null. If so, create it. Always return instance.

Hint 2: Preventing direct instantiation

Make the constructor private (TypeScript) or use __new__ override (Python) to prevent direct new Config() calls.

Solution

TypeScript

class Config {
  private static instance: Config | null = null;
  private data: Map<string, unknown> = new Map();

  private constructor() {}

  static getInstance(): Config {
    if (!Config.instance) {
      Config.instance = new Config();
    }
    return Config.instance;
  }

  get<T>(key: string): T | undefined {
    return this.data.get(key) as T | undefined;
  }

  set(key: string, value: unknown): void {
    this.data.set(key, value);
  }

  has(key: string): boolean {
    return this.data.has(key);
  }

  clear(): void {
    this.data.clear();
  }
}

// Usage
const config = Config.getInstance();
config.set("apiUrl", "https://api.example.com");
config.set("timeout", 5000);

// Same instance everywhere
const sameConfig = Config.getInstance();
sameConfig.get<string>("apiUrl"); // "https://api.example.com"

Python

from typing import Any

class Config:
    _instance: "Config | None" = None
    _data: dict[str, Any]

    def __new__(cls) -> "Config":
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._data = {}
        return cls._instance

    def get(self, key: str, default: Any = None) -> Any:
        return self._data.get(key, default)

    def set(self, key: str, value: Any) -> None:
        self._data[key] = value

    def has(self, key: str) -> bool:
        return key in self._data

# Usage
config1 = Config()
config1.set("debug", True)

config2 = Config()
config2.get("debug")  # True
config1 is config2    # True

PHP

class Config {
    private static ?self $instance = null;
    private array $data = [];

    private function __construct() {}
    private function __clone() {}

    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function get(string $key, mixed $default = null): mixed {
        return $this->data[$key] ?? $default;
    }

    public function set(string $key, mixed $value): void {
        $this->data[$key] = $value;
    }
}

When to Avoid Singletons

Singletons introduce hidden global state. Consider these alternatives:

ProblemAlternative
Need shared stateDependency injection
Need one instanceDI container with singleton scope
Need global configModule-level constants or environment variables
Need a registryPass the registry explicitly

Testing difficulty: Singletons carry state between tests. You’d need a reset() method or constructor injection to make them testable.

Complexity Analysis

O(1) for all operations. The pattern adds no computational overhead — it’s purely about object lifecycle management.

Cross-Language Notes

TypeScript/JavaScript: ES modules are cached after first import. Exporting an object from a module is effectively a singleton without the boilerplate:

// config.ts
export const config = new Map<string, unknown>();

Every import gets the same config instance.

Python: Same principle — a module-level object is a singleton. Python’s module caching (sys.modules) ensures a module’s top-level code runs only once:

# config.py
_config: dict[str, Any] = {}
def get(key: str) -> Any: return _config.get(key)
def set(key: str, value: Any) -> None: _config[key] = value

PHP: PHP’s shared-nothing architecture (each request starts fresh) means singletons only live within a single request. This actually makes them safer than in long-running processes. In frameworks like Symfony/Laravel, the DI container manages singleton services.

Rust: Rust makes singletons intentionally difficult (requiring unsafe or OnceCell/LazyLock) to discourage global mutable state. This is a deliberate design choice.


Suggest edit
Share this post on:

Previous Post
Kata: Binary Search
Next Post
Kata: Factory Pattern