Skip to main content

Singleton

Overview​

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

When to use​

  • A single shared resource is required (for example, config or a process-wide cache).
  • You need controlled, lazy access to that resource.

Java example​

public final class ConfigRegistry {
private final Properties props;

private ConfigRegistry() {
this.props = loadProps();
}

public static ConfigRegistry getInstance() {
return Holder.INSTANCE;
}

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

private static Properties loadProps() {
return new Properties();
}

private static final class Holder {
private static final ConfigRegistry INSTANCE = new ConfigRegistry();
}
}

TypeScript example​

class ConfigRegistry {
private static instance: ConfigRegistry | null = null;
private readonly props: Record<string, string>;

private constructor() {
this.props = loadConfig();
}

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

get(key: string): string | undefined {
return this.props[key];
}
}

function loadConfig(): Record<string, string> {
return { env: 'prod' };
}

Pros and cons​

Pros:

  • Controlled access to a single instance.
  • Lazy initialization is possible.

Cons:

  • Global state makes tests harder.
  • Hidden dependencies can spread.

Common pitfalls​

  • Using Singleton as a default for shared state.
  • Thread-safety issues in lazy initialization.
  • Hard-coded instance creation that blocks testing.