← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Singleton (Anti-Pattern)

General Intermediate
debt(d5/e7/b7/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5), because detection_hints lists phpstan and phpmd — both specialist static analysis tools. The code_pattern (getInstance() static method, private constructor with static $instance) is detectable but requires these tools to flag it systematically; a default linter won't catch it.

e7 Effort Remediation debt — work required to fix once spotted

Closest to 'cross-cutting refactor across the codebase' (e7), because the quick_fix says to replace Singletons with dependency injection, which requires touching every call site that references the global instance, updating constructors to accept injected dependencies, and potentially wiring a DI container — a cross-cutting change across the codebase rather than a localised fix.

b7 Burden Structural debt — long-term weight of choosing wrong

Closest to 'strong gravitational pull' (e7), because applies_to covers web, cli, and queue-worker contexts and the misconception confirms Singletons create hidden global state that couples all consuming code to a specific implementation. Every component that depends on the Singleton is shaped by that choice, making it a strong gravitational burden on future changes and test isolation.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7), because the misconception explicitly states developers believe Singletons are a clean sharing mechanism, when in reality they introduce hidden global state, break test isolation (state persists between tests), and hide dependencies — behaviours that contradict the expectation of a well-scoped design pattern. The 'obvious' use is regularly wrong in practice.

About DEBT scoring →

Also Known As

singleton pattern single instance global instance

TL;DR

A class that restricts instantiation to a single instance — widely considered an anti-pattern due to hidden global state and testability issues.

Explanation

The Singleton pattern guarantees only one instance of a class exists and provides a global access point. While it solves real problems (single database connection, single logger), it introduces global state that makes classes dependent on Singleton internals, complicates testing (can't substitute mock implementations easily), and violates Dependency Inversion. The preferred alternative is using a Dependency Injection container to manage instance lifetime — register services as shared/singleton in the container, inject them via constructors.

Common Misconception

Singletons are a clean way to share a single instance across an application. Singletons are global state disguised as a pattern — they make unit testing nearly impossible (the instance persists between tests), hide dependencies, and create tight coupling. Dependency injection achieves the same single-instance goal without these problems.

Why It Matters

The Singleton pattern ensures one instance of a class exists globally — but it creates hidden global state that makes testing difficult and couples code to a specific implementation.

Common Mistakes

  • Using Singleton for services that should be injected — it prevents swapping implementations in tests.
  • Singleton that is not thread-safe in async or multi-process contexts.
  • Singletons that accumulate state across tests — tests must reset singleton state to be isolated.
  • Using Singleton when dependency injection containers already manage instance lifetime.

Code Examples

💡 Note
The Singleton itself isn't the problem — global mutable state is. Let a DI container manage instance lifetime instead.
✗ Vulnerable
class Config {
    private static ?self $instance = null;
    private function __construct() {}
    public static function getInstance(): self {
        self::$instance ??= new self();
        return self::$instance;
    }
    // Testing is impossible — can't inject a different Config
    // Shared mutable state across the application
}
✓ Fixed
// Use DI container to manage single instance instead
// config/app.php (Laravel service container)
$app->singleton(Config::class, fn() => new Config(getenv('APP_ENV')));

// Injected wherever needed — mockable in tests
class AppController {
    public function __construct(private Config $config) {}
}

Added 15 Mar 2026
Edited 22 Mar 2026
Views 43
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 1 ping W 1 ping T 0 pings F 0 pings S 2 pings S 1 ping M 0 pings T 1 ping W 2 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 1 ping W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W
No pings yet today
PetalBot 1
ChatGPT 6 Google 5 Scrapy 5 Ahrefs 4 Unknown AI 3 SEMrush 2 Perplexity 1 Claude 1 Meta AI 1 Sogou 1 PetalBot 1
crawler 25 crawler_json 4 pre-tracking 1
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: High
⚡ Quick Fix
Replace Singletons with dependency injection — a DI container can manage single instances without the global state problem; Singleton makes testing impossible because you can't swap the instance
📦 Applies To
any web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
getInstance() static method; private constructor with static $instance; global state via Singleton breaking test isolation
Auto-detectable: ✓ Yes phpstan phpmd
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: High Context: Class Tests: Update


✓ schema.org compliant