SE

Creational patterns

Must-know concept1 hIntermediate

Singleton, Factory, Builder — when and why.

Creational patterns answer the question "how does this object come into existence?" The goal isn't to use every pattern — it's to recognise the moment when constructing an object directly is starting to hurt, and reach for the right shape.

The big idea

Direct construction (new Order(…)) is great until one of three pains shows up:

Need one shared instance
Need to vary the concrete type
Need many optional params

Each pain has a pattern. Use the pattern only when the pain is real.

Singleton — exactly one instance

A class that owns its single instance and hands it out forever. Reaches for a global, in disguise.

class Logger {
  private static instance: Logger;
  private constructor(private readonly minLevel: 'info' | 'warn' = 'info') {}
 
  static get(): Logger {
    if (!Logger.instance) Logger.instance = new Logger();
    return Logger.instance;
  }
}
 
Logger.get().info('boot');

Factory — hide the construction

When the choice of concrete class depends on input, a factory keeps callers from caring.

Do
function createPayment(method: 'card' | 'bank' | 'crypto'): Payment {
  switch (method) {
    case 'card':   return new StripeCardPayment();
    case 'bank':   return new AchBankPayment();
    case 'crypto': return new BitcoinPayment();
  }
}

Callers say what they want; the factory decides how.

Don't
// every caller branches on the same method
if (method === 'card')   p = new StripeCardPayment();
else if (method === 'bank') p = new AchBankPayment();
// …repeated everywhere

Add a new method and you grep for the conditional. Painful.

Builder — many optional parameters

Constructors with 8 boolean flags become unreadable. A builder gives each piece a name and lets the user opt in.

const request = new HttpRequest.Builder('https://api.example.com/orders')
  .method('POST')
  .header('Content-Type', 'application/json')
  .header('Authorization', 'Bearer …')
  .body(JSON.stringify({ sku: 'X1' }))
  .timeoutMs(2000)
  .retries(3)
  .build();

A keyword-arguments "options bag" achieves the same readability without a separate builder class:

const request = httpRequest({
  url: 'https://api.example.com/orders',
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: { sku: 'X1' },
  timeoutMs: 2000,
  retries: 3,
});

That's the same intent — pick whichever style your stack favours.

Other creational patterns you'll meet

Abstract factory
A factory that builds families of related objects (e.g., UI components for an OS theme). Common in GUI toolkits, rare elsewhere.
Prototype
Cloning an existing object instead of constructing one. Useful when construction is expensive or stateful.
DI container
Not strictly a GoF pattern, but it's where creational logic lives in modern apps (Spring, NestJS, Angular).

In practice

Most modern codebases barely use Singleton (DI containers replaced it), use Factory often without naming it, and use Builder via TypeScript options objects. Knowing the names lets you read older code and understand interviews.

Key takeaways

  • Singletons are easy to overuse; prefer DI for testability.
  • A factory hides which concrete class is built; callers stay decoupled.
  • Builder (or an options object) cures constructors with too many parameters.
  • Modern frameworks (DI containers) cover most creational needs out of the box.
  • The patterns are vocabulary, not laws — use them when they reduce pain.

Checkpoint questions

Use these to test whether the lesson is clear enough to explain without rereading.

  1. 1When is a Factory clearer than calling constructors directly?
  2. 2What problem does Builder solve when an object has many optional fields?
  3. 3Why is Singleton often risky in tests and concurrent systems?
  4. 4Can you name the construction concern each creational pattern hides?

References

External resources for going deeper after the lesson above.