Singleton Pattern: The Most Controversial Design Pattern

One instance to rule them all - but should it?

The Problem

Imagine your app has multiple parts that need access to the same resource:

  • Configuration settings loaded from a file
  • A single database connection pool
  • A logging system that writes to one file

Without coordination, you'd create duplicates wastefully.

Creating multiple instances wastes memory and causes inconsistency.

The Solution: Singleton Pattern

Ensure a class has only ONE instance and provide a global access point to it.

Key characteristics:

  • Private constructor - prevents new Singleton()
  • Static instance - holds the single object
  • Static accessor method - getInstance() returns the instance

Implementation: Eager vs Lazy

Eager Initialization

class Logger {
  private static instance = new Logger()
  private constructor() {}

  static getInstance() {
    return Logger.instance
  }
}

Created immediately when class loads.

Lazy Initialization

class DatabasePool {
  private static instance: DatabasePool
  private constructor() {
    // expensive setup
  }

  static getInstance() {
    if (!DatabasePool.instance) {
      DatabasePool.instance = new DatabasePool()
    }
    return DatabasePool.instance
  }
}

Created only when first requested - saves resources if unused.

Real-World Use Cases

When you genuinely need one shared instance:

  • Configuration Manager - app settings loaded once
  • Logger - all modules write to same log file
  • Database Connection Pool - share connections efficiently
  • Cache - single source of truth for cached data
  • Thread Pool - coordinate worker threads

Use sparingly - only when multiple instances cause real problems.

When to Use (and When NOT to Use)

Use When:

  • You need exactly one instance by design
  • Resource must be shared globally
  • Multiple instances would cause bugs or waste

Avoid When:

  • You just want convenient global access (use DI instead)
  • You're tempted to use it for everything
  • Your class has no inherent reason to be singular

Why Singleton Gets Criticized

Often called an "anti-pattern" due to:

  • Hidden dependencies - not clear what classes depend on it
  • Global state - makes testing difficult
  • Tight coupling - hard to swap implementations
  • Violates Single Responsibility - manages both its logic AND its lifecycle
  • Testing nightmares - singletons persist between tests

Modern alternative: Dependency Injection

The Better Alternative: Dependency Injection

// Instead of this (Singleton):
const logger = Logger.getInstance()
logger.log('message')

// Do this (Dependency Injection):
class UserService {
  constructor(private logger: Logger) {}

  createUser(data) {
    this.logger.log('Creating user')
  }
}

// Create one logger, inject where needed
const logger = new Logger()
const userService = new UserService(logger)

Makes dependencies explicit and testable.

Key Takeaways

  • Singleton ensures one instance with global access
  • Use for genuine shared resources (config, logging, pools)
  • Lazy vs Eager initialization trade-off
  • Often an anti-pattern when overused
  • Causes testing and coupling issues
  • Prefer Dependency Injection for flexibility

Remember: Just because you CAN make it a singleton doesn't mean you SHOULD.

1 / 0
Singleton Pattern Explained