Creational patterns answer one question: who's allowed to call new, and how? They centralize construction so the rest of the code stays free of "which concrete class do I instantiate here?" decisions. Some are timeless; one (Singleton) has aged into an anti-pattern in modern code.
new ConcreteClass() hard-codes a dependency. Callers can't substitute, can't test, can't extend.Intent: Define an interface for creating an object, but let subclasses (or configuration) decide which concrete class to instantiate.
A method returns an object whose concrete type can vary. Callers depend on the abstract return type; the factory picks the implementation. Common in plugin systems, platform-specific code, and "give me a logger / cache / parser based on this name."
Example: Logger.create("file") returns a FileLogger; Logger.create("syslog") returns a SyslogLogger; both expose the same Logger interface.
When to use: the caller cares about the abstraction, not the concrete class. The choice depends on runtime config, OS, or content type.
Intent: Provide an interface for creating families of related objects without specifying their concrete classes.
A factory of factories. WindowsUIFactory creates Windows-styled buttons, scrollbars, menus; MacUIFactory creates Mac-styled ones. Switch the factory, switch the whole skin coherently.
Modern echoes: cloud SDKs use this — AWSClientFactory hands you S3, SQS, DynamoDB clients all configured against the same region and credentials.
Watch out: easy to over-apply. If the family is "one thing," it's just a Factory Method.
Intent: Construct a complex object step by step, separate from its representation, so the same construction process can produce different results.
Especially nice for immutable objects with many optional fields. Avoids constructor explosion (12 nullable parameters in some specific order). The Builder collects parameters fluently and produces the final object on build().
HttpRequest req = HttpRequest.builder()
.url("https://api.example.com/orders")
.header("Authorization", token)
.timeout(Duration.ofSeconds(5))
.body(payload)
.build();
Modern alternatives: named/default arguments (Python, Kotlin, Swift, C#) cover most Builder use cases without the boilerplate. Java/older C++ still benefit from explicit Builders. Lombok's @Builder generates them.
Intent: Create new objects by cloning an existing prototype rather than instantiating from scratch.
Useful when construction is expensive (loaded a 50 MB model? clone, don't reload), or when the prototype carries pre-configured state you want to start from. JavaScript's whole object model is built on prototypes; in classical OO languages, you implement clone().
Watch out: shallow vs deep clone. Cloning an object that holds a reference to a mutable collection without deep-copying the collection is a classic source of nasty aliasing bugs.
Intent: Ensure exactly one instance of a class exists, and provide a global point of access.
Sounds tidy. In practice it's hidden global state with predictable consequences: untestable code (you can't substitute the singleton in a test), hidden coupling (callers reach into a static accessor), lifecycle issues (when does it start? when does it stop? what about thread safety?).
Modern advice: if you need exactly one of something, register it as a singleton in your DI container and inject it where it's needed. Don't expose a static accessor. The pattern's "global access" promise is precisely what makes it harmful.
Acceptable uses: truly process-wide concerns where injection is impractical — a logger root, a metrics registry. Even then, prefer DI when you can.
Not in the original GoF, but creational in spirit. Pre-create a pool of expensive objects (DB connections, thread workers, large buffers) and check them in/out instead of creating fresh. Used by every connection pool, thread pool, and buffer pool you've ever touched.
Most of the original creational-pattern motivation — "decouple callers from the concrete class they instantiate" — is now solved by Dependency Injection at the application level. Spring, ASP.NET DI, NestJS, Guice, Dagger all let you wire concrete implementations once, in a composition root, and inject them through constructors.
You still see Factory, Abstract Factory, and Builder in: