What It Is
The Singleton pattern makes sure a class has exactly one instance in the whole program, and gives everyone the same access point to it. Typical singletons: a configuration, a connection pool, a logger.
TypeScript gets this almost for free. A module is loaded and run once, and everything it creates is cached and shared by every later import. So a value stored at module level — including a static field on a class — already exists exactly once. The pattern only adds two things: a private constructor, so nobody can build a second one, and a static accessor that creates the value on first use.
Example: One Config for Everyone
The config is loaded on the first Config.getInstance() call and never again. Every caller receives the same object.
Load once, keep it in a static field
The "loading config" line prints only once, although Config.getInstance() is called twice — and a === b proves both callers hold the same object. The ??= operator assigns only when the field is still undefined, which is exactly "create it the first time".
Output: TypeScript file and captured output
class Config {
readonly appName: string
// Private: no code outside this class can build a second Config.
private constructor() {
console.log("loading config (happens once)")
this.appName = "PromptCookbook"
}
// The one instance lives here, created on first use and kept forever.
private static instance: Config | undefined
static getInstance(): Config {
Config.instance ??= new Config()
return Config.instance
}
}
const a = Config.getInstance()
const b = Config.getInstance()
console.log("app:", a.appName)
console.log("same instance:", a === b)
When to Use It
Use it when there really must be one of something: one loaded configuration, one connection pool, one cache — and creating it is expensive or duplicates would conflict.
Skip it when you can simply create the value once at the top of your program and pass it to whoever needs it. Passing dependencies openly is clearer than global access — the singleton is the last resort, not the first.
Pros and Cons
Pros: guaranteed single instance; the private constructor makes a second one impossible; creation is lazy, so nothing is built until someone asks.
Cons: it is a global in disguise — hidden dependencies make code harder to follow; tests cannot easily swap it for a fake, and one test can leave state behind for the next; overuse leads to tangled programs.