Singleton: One Instance, Guaranteed
Singleton: One Instance, Guaranteed
The point of a singleton is controlling construction so every caller shares one instance — a config store, a connection pool, a logger.
class ConnectionPool {
private static instance: ConnectionPool | null = null;
private constructor(public readonly size: number) {}
static get(size: number): ConnectionPool {
return (ConnectionPool.instance ??= new ConnectionPool(size));
}
}
The private constructor blocks new; the static accessor memoizes. In TypeScript the simplest singleton is usually a module: top-level state runs once per process, and export const pool = ... gives the same guarantee with no class machinery.
Factories: Construction Behind an Interface
Factories: Construction Behind an Interface
A factory returns a type instead of exposing new, which keeps branching and setup in one place:
type Repo = PgRepo | SqliteRepo | MemoryRepo;
function makeRepo(cfg: Config): Repo {
switch (cfg.driver) {
case "pg": return new PgRepo(cfg.url);
case "sqlite": return new SqliteRepo(cfg.file);
default: return new MemoryRepo();
}
}
An abstract factory groups related creations — UI kits producing matching button and input implementations per platform. The return type can be a discriminated union, so callers narrow with if (repo.kind === "pg") and get precise fields instead of any.
Pitfalls and Interview Angles
Pitfalls and Interview Angles
Singletons are ambient state. A module-level instance is invisible in signatures, survives across tests, and resists stubbing — pass it as a constructor parameter instead, keeping a default for convenience.
Factory vs switch soup. When the branch list grows, registries beat nested conditionals:
const drivers: Record<string, (c: Config) => Repo> = { pg: makePg, sqlite: makeSqlite };
Mocking. Factories are trivially swappable in tests; singletons need reset hooks.
Interview: defend a singleton only when the invariant truly is "one per process", then show how constructor injection removes the hidden dependency while keeping the shared instance at the composition root.
Quiz
1. What does a private constructor buy you in a singleton class?
2. Main testing drawback of a singleton versus constructor injection?
3. When is a factory clearly better than a chain of if/else construction?
Flashcards
Question
Singleton
Click to reveal answer
Answer
One shared instance per process; private constructor plus static accessor, or simply module scope.
Question
Factory function
Click to reveal answer
Answer
Returns an instance behind a type, hiding branching and setup from callers.
Question
Abstract factory
Click to reveal answer
Answer
Family of related creators producing compatible products (e.g. per-platform UI controls).
Question
Ambient state
Click to reveal answer
Answer
Globals a signature does not mention — the main reason singletons hurt testability.
Revision Notes
Key Takeaways
- 1. Module scope is the idiomatic TypeScript singleton
- 2. Factories replace branching construction and return unions callers can narrow
- 3. Inject the shared instance from a composition root instead of importing it deep in the tree
Interview Tips
- • State the invariant first (one per process?) before defending the pattern
- • Show how you would reset or stub the singleton in a test suite
Cheat Sheet
Singleton: private ctor + static memo, or module export; factory hides branching, returns Repo union; inject from composition root to keep tests clean.
Continue Learning
Before You Start
- No prerequisites listed.