Waqas Ahmad — Software Architect & Technical Consultant - Available USA, Europe, Global

Waqas Ahmad — Software Architect & Technical Consultant

Specializing in

Distributed Systems

.NET ArchitectureCloud-Native ArchitectureAzure Cloud EngineeringAPI ArchitectureMicroservices ArchitectureEvent-Driven ArchitectureDatabase Design & Optimization

👋 Hi, I'm Waqas — a Software Architect and Technical Consultant specializing in .NET, Azure, microservices, and API-first system design..
I help companies build reliable, maintainable, and high-performance backend platforms that scale.

Experienced across engineering ecosystems shaped by Microsoft, the Cloud Native Computing Foundation, and the Apache Software Foundation.

Available for remote consulting (USA, Europe, Global) — flexible across EST, PST, GMT & CET.

services
Article

Creational Design Patterns in .NET: All 5 Patterns with Full Working Code

GoF creational patterns in .NET: Factory, Builder, Singleton. With C# examples.

services
Read the article

Introduction

This guidance is relevant when the topic of this article applies to your system or design choices; it breaks down when constraints or context differ. I’ve applied it in real projects and refined the takeaways over time (as of 2026).

Client code that depends directly on concrete types and construction details becomes hard to test and change when creation logic or product families evolve. This article covers all five GoF creational patterns—Factory Method, Abstract Factory, Builder, Prototype, and Singleton—with definitions, when to use each, class diagrams, and full working C# examples. For architects and tech leads, choosing the right creational pattern keeps object creation decoupled and testable and avoids over-engineering or hidden singletons that block scaling.

If you are new to creational patterns, start with All creational patterns at a glance and jump to the pattern you need.

For a deeper overview of this topic, explore the full .NET Architecture guide.

What are creational patterns?

Creational patterns describe recurring ways to create objects: delegating creation to a method or subclass (Factory Method), to a family of factories (Abstract Factory), to a step-by-step builder (Builder), by cloning (Prototype), or by ensuring a single instance (Singleton). There are five creational patterns; the table below lists all of them.

Pattern Problem it solves Typical .NET use
Factory Method Client should not depend on concrete product type; creation varies by context Override factory method in base creator class
Abstract Factory Need a family of related products (e.g. Win vs Mac UI) Interface + concrete factories, register in DI
Builder Complex construction, many optional parameters Fluent builder class with Build()
Prototype Cloning cheaper or safer than new ICloneable, copy constructor, or clone method
Singleton Exactly one instance (logger, config, pool) AddSingleton<T> in DI; avoid static

Decision Context

  • System scale: Applies when object creation is non-trivial (e.g. multiple representations, families of objects, or controlled lifecycle); creational patterns (Singleton, Factory, Builder, Prototype, Abstract Factory) are applied per component or layer.
  • Team size: One to several teams; someone must recognise when creation logic deserves a pattern so the codebase doesn’t scatter new and construction logic.
  • Time / budget pressure: Fits when you have time to introduce a factory or builder; overkill for trivial new in one place.
  • Technical constraints: .NET and C#; patterns map to static factories, DI, and interfaces; no special framework required.
  • Non-goals: This article does not optimize for “use every creational pattern everywhere”; it optimises for choosing the right pattern when creation is complex or must be abstracted.

All creational patterns at a glance

  • Factory Method: One method creates one product; subclasses override to create concrete products. Use when creation varies by subclass (e.g. PDF vs Word document).
  • Abstract Factory: A family of factories for related products. Use when you need consistent families (e.g. all Win UI or all Mac UI).
  • Builder: Separate construction from representation; fluent API with optional steps. Use for complex objects with many optional parameters.
  • Prototype: Clone an existing instance instead of creating from scratch. Use when cloning is cheaper than new or you need copies with small variations.
  • Singleton: Exactly one instance of a class. In .NET prefer DI AddSingleton<T> so tests can replace it.

Factory Method pattern

What it is and when to use it

Factory Method defines a method that creates an object; subclasses override the method to create concrete products. The client depends only on the creator abstraction and the product interface—never on concrete product types. Use it when creation varies by context (e.g. PDF vs Word document, DB vs HTTP connection) and you want to add new product types by adding new creator subclasses, not by changing client code. Typical uses: document/export pipelines, connection creators, serializers, and anywhere one product type is created but the concrete type depends on configuration or subclass.

Class structure

Loading diagram…

Class structure explained: The Creator (abstract base) declares FactoryMethod() returning IProduct; it may also call that method in SomeOperation(). ConcreteCreatorA and ConcreteCreatorB override FactoryMethod() to return ConcreteProductA and ConcreteProductB respectively. The client uses only Creator and IProduct—so adding a new product means adding a new creator subclass and new product class, without touching existing client code.

Full working example: Document export pipeline

1. Product interface and concrete products

namespace FactoryMethodExample;
public interface IDocument { byte[] Export(); }
public class PdfDocument : IDocument { public byte[] Export() => /* render PDF */ Array.Empty<byte>(); }
public class WordDocument : IDocument { public byte[] Export() => /* render Word */ Array.Empty<byte>(); }

2. Creator (abstract) and concrete creators

namespace FactoryMethodExample;
public abstract class DocumentCreator {
    public abstract IDocument CreateDocument();
    public byte[] Export(string data) {
        var doc = CreateDocument();
        return doc.Export();
    }
}
public class PdfCreator : DocumentCreator {
    public override IDocument CreateDocument() => new PdfDocument();
}
public class WordCreator : DocumentCreator {
    public override IDocument CreateDocument() => new WordDocument();
}

3. Client using only creator abstraction

DocumentCreator creator = new PdfCreator();
byte[] bytes = creator.Export("data");

How this code fits together: The client holds a DocumentCreator and calls Export (or CreateDocument directly). The creator’s Factory Method (CreateDocument) is overridden in PdfCreator and WordCreator to return the right IDocument implementation. The client never references PdfDocument or WordDocument—only the creator type and IDocument. Adding Excel is done by adding ExcelDocument and ExcelCreator.

When to use Factory Method: Use when you have one product type (e.g. IDocument) but creation varies by context or subclass and you want the client decoupled from concrete products. Avoid when you need families of related products (use Abstract Factory) or when construction is complex and stepwise (use Builder).


Abstract Factory pattern

What it is and when to use it

Abstract Factory provides an interface for creating families of related products (e.g. Win button + Win text box, or Mac button + Mac text box). Concrete factories implement that interface and produce consistent families. Use it when you have multiple product types that must be used together (e.g. UI theme: all Win or all Mac) and you want to avoid mixing products from different families. Typical uses: UI themes (Win/Mac/Linux), cross-platform widgets, database drivers (SQL Server vs PostgreSQL), and product families (modern vs legacy API clients).

Class structure

Loading diagram…

Class structure explained: IAbstractFactory declares methods that create IProductA and IProductB. ConcreteFactory1 and ConcreteFactory2 implement the interface and return concrete products from their respective families (e.g. Win vs Mac). The client receives an IAbstractFactory (e.g. via DI) and calls CreateProductA() and CreateProductB(); it never sees concrete product classes, and swapping the factory swaps the whole family.

Full working example: UI factory (Win vs Mac)

1. Product interfaces and concrete products

namespace AbstractFactoryExample;
public interface IButton { string Render(); }
public interface ITextBox { string Render(); }
public class WinButton : IButton { public string Render() => "WinButton"; }
public class WinTextBox : ITextBox { public string Render() => "WinTextBox"; }
public class MacButton : IButton { public string Render() => "MacButton"; }
public class MacTextBox : ITextBox { public string Render() => "MacTextBox"; }

2. Abstract factory and concrete factories

namespace AbstractFactoryExample;
public interface IUiFactory {
    IButton CreateButton();
    ITextBox CreateTextBox();
}
public class WinUiFactory : IUiFactory {
    public IButton CreateButton() => new WinButton();
    public ITextBox CreateTextBox() => new WinTextBox();
}
public class MacUiFactory : IUiFactory {
    public IButton CreateButton() => new MacButton();
    public ITextBox CreateTextBox() => new MacTextBox();
}

3. Client and registration

IUiFactory factory = new WinUiFactory(); // or from DI: GetRequiredService<IUiFactory>()
var btn = factory.CreateButton();
var box = factory.CreateTextBox();
// btn and box are from same family (Win)

How this code fits together: The client depends only on IUiFactory, IButton, and ITextBox. You register one concrete factory (e.g. WinUiFactory or MacUiFactory) in DI; that factory creates all UI widgets for one theme. Changing the registered factory changes the entire UI family without changing client code.

When to use Abstract Factory: Use when you need families of related products and callers must not mix products from different families. Avoid when you have only one product type (use Factory Method) or when products are independent (a simple factory or DI may suffice).


Builder pattern

What it is and when to use it

Builder separates the construction of a complex object from its representation, so the same construction process can create different representations. A Director (optional) orchestrates the builder; the Builder exposes fluent methods for each step and a Build() that returns the product. Use it when an object has many optional parameters or stepwise construction and you want readable, valid construction (e.g. require certain steps, validate as you go). Typical uses: complex DTOs, HTTP request builders, query builders, configuration objects, and any object with many optional or stepwise fields.

Class structure

Loading diagram…

Class structure explained: IBuilder defines the construction steps and GetResult(). ConcreteBuilder implements those steps and builds a Product step by step. The Director holds an IBuilder and calls the steps in a fixed order in Construct(); the client can also call the builder directly for more control. The client gets the product only after calling GetResult() (or Build() in fluent style).

Full working example: Order builder (fluent)

1. Product (Order)

namespace BuilderExample;
public class Order {
    public string Id { get; set; }
    public string Customer { get; set; }
    public List<LineItem> Items { get; set; } = new();
}
public class LineItem { public string Sku { get; set; } public int Qty { get; set; } }

2. Fluent builder

namespace BuilderExample;
public class OrderBuilder {
    private string _id;
    private string _customer;
    private readonly List<LineItem> _items = new();
    public OrderBuilder WithId(string id) { _id = id; return this; }
    public OrderBuilder ForCustomer(string c) { _customer = c; return this; }
    public OrderBuilder WithItem(string sku, int qty) { _items.Add(new LineItem { Sku = sku, Qty = qty }); return this; }
    public Order Build() => new Order { Id = _id, Customer = _customer, Items = _items.ToList() };
}

3. Usage

var order = new OrderBuilder()
    .WithId("O1")
    .ForCustomer("Alice")
    .WithItem("SKU1", 2)
    .WithItem("SKU2", 1)
    .Build();

How this code fits together: The client uses OrderBuilder’s fluent API to set Id, Customer, and items step by step, then calls Build() to get an immutable Order. Construction logic and validation (if any) live in the builder; the Order class stays a simple data holder. No telescoping constructors and no optional parameters overload.

When to use Builder: Use when you have complex construction with many optional parameters or stepwise setup and want readable, valid object creation. Avoid when you have few required parameters—a constructor or object initializer is enough.


Prototype pattern

What it is and when to use it

Prototype creates new objects by cloning an existing instance (the prototype) instead of building from scratch. Use it when cloning is cheaper or safer than new (e.g. after expensive load from DB or file), or when you need copies with small variations (e.g. report template with different title). Typical uses: report templates, game entities (spawn from template), cached configurations, and any “copy and modify” scenario. In .NET you can use ICloneable, a copy constructor, or a custom Clone() method; be explicit about shallow vs deep copy.

Class structure

Loading diagram…

Class structure explained: IPrototype (or a base type) declares Clone(). ConcretePrototypeA and ConcretePrototypeB implement cloning by creating a copy of their state (shallow or deep). The client obtains a prototype instance (e.g. from a registry or cache) and calls Clone() to get a new object without knowing concrete types or construction details.

Full working example: Report template

1. Prototype with clone

namespace PrototypeExample;
public class ReportTemplate {
    public string Title { get; set; }
    public List<string> Sections { get; set; } = new();
    public ReportTemplate Clone() =>
        new ReportTemplate {
            Title = Title,
            Sections = new List<string>(Sections)
        };
}

2. Usage

var original = new ReportTemplate { Title = "Q1", Sections = new List<string> { "Intro", "Data" } };
var copy = original.Clone();
copy.Title = "Q2 Report";

How this code fits together: The client holds a ReportTemplate (e.g. loaded once from config or DB). Instead of building a new template from scratch, it calls Clone() to get a copy and then mutates the copy (e.g. title, sections). The prototype holds shared or default state; cloning avoids repeating expensive setup.

When to use Prototype: Use when cloning is cheaper or safer than full construction, or when you need copies with small variations. Be explicit about shallow vs deep: shallow copies share nested references; deep copies duplicate nested objects. Avoid when construction is simple and there is no shared default state.


Singleton pattern

What it is and when to use it

Singleton ensures a class has exactly one instance and provides a global access point to it. In .NET, prefer dependency injection with AddSingleton<T> so the same instance is shared wherever T is injected—and tests can replace it with a mock. Avoid static singleton fields when you need testability. Use it for loggers, application config, connection pools, cache managers, and thread pools. The “single instance” is managed by the DI container, not by a static property.

Class structure

Loading diagram…

Class structure explained: The client depends on IService (interface). SingletonService implements it. The container registers AddSingleton<IService, SingletonService>(), so one instance of SingletonService is created and reused for all requests of IService. No static instance—the container owns the lifetime.

Full working example: Logger via DI

1. Interface and implementation

namespace SingletonExample;
public interface IAppLogger { void Log(string message); }
public class FileLogger : IAppLogger {
    public void Log(string message) { /* write to file */ }
}

2. Registration and usage

// Program.cs or Startup
services.AddSingleton<IAppLogger, FileLogger>();

// Consumer: same instance injected everywhere
public class OrderService {
    public OrderService(IAppLogger logger) { /* one shared logger */ }
}

How this code fits together: The DI container creates one FileLogger and injects it wherever IAppLogger is requested. All consumers share the same instance. In tests you register a fake IAppLogger instead. Avoid private static readonly FileLogger _instance = new FileLogger();—it makes testing and replacement hard.

When to use Singleton: Use when you need exactly one instance of a service (logger, config, pool, cache) and you want it shared across the app. In .NET use AddSingleton in DI. Avoid static singleton when testability or replacement matters.


Comparison: when to use which

Pattern Use when Avoid when
Factory Method One product type; creation varies by subclass You need families of products (use Abstract Factory)
Abstract Factory Families of related products (e.g. UI theme) Single product type (use Factory Method)
Builder Complex object, many optional params or steps Few parameters; constructor suffices
Prototype Cloning cheaper than new; copies with variations Simple construction; no shared default state
Singleton Exactly one instance (logger, config, pool) You need testability without DI (prefer DI singleton)

Common pitfalls

  • Factory Method: Letting the client depend on concrete creators or products; keep client code against the creator abstraction and product interface only.
  • Abstract Factory: Mixing products from different families (e.g. Win button with Mac text box); ensure one factory instance per family.
  • Builder: Forgetting to validate required fields before Build(), or making the product mutable in a way that breaks after Build().
  • Prototype: Shallow cloning when nested objects are mutable—clone nested structures too (deep copy) or document that clones share references.
  • Singleton: Using static singleton instead of DI—hard to test and replace; prefer AddSingleton<T> and inject the interface.


Position & Rationale

I use Factory Method when I need to defer object creation to a subclass or to keep creation out of the main flow; I use Abstract Factory when I have families of related products and want to swap the whole family. I use Builder when construction has many optional steps or the same steps in different orders; I avoid it for simple DTOs. I use Prototype when cloning is cheaper or more appropriate than creating from scratch (e.g. templates, copy). I use Singleton sparingly—only for true app-wide single instances (e.g. logger, config); I prefer DI and single registration over a static Singleton. I reject using Abstract Factory when I have only one product family; I reject Singleton when a scoped or transient service would do. I combine creational patterns with DI so the container owns lifetime and I don’t hide dependencies.


Trade-Offs & Failure Modes

  • What this sacrifices: Each pattern adds types and indirection; over-use makes creation paths hard to follow. Singleton bypasses DI and makes testing and multi-instance scenarios harder.
  • Where it degrades: Abstract Factory with one implementation—ceremony. Builder for three-field DTOs—overkill. Singleton for something that could be scoped (e.g. per-request)—bugs when concurrency or test isolation matter.
  • How it fails when misapplied: Using Singleton for services that need to be replaced or tested in isolation. Using Abstract Factory when a single factory method would do. Using Builder when a constructor or object initialiser is enough.
  • Early warning signs: “We need two instances but everything is Singleton”; “the builder has one optional field”; “we added Abstract Factory but we only ever use one family.”

What Most Guides Miss

Guides show each pattern in isolation and rarely help you choose. In practice, Factory Method vs Abstract Factory depends on whether you have one product or families; Builder pays off when there are many optional parameters or complex validation, not for every DTO. Singleton is widely overused; in .NET, DI with single registration (e.g. AddSingleton) gives the same single instance with testability and explicit dependency. Prototype is often skipped in C# because we have object initialisers and copy constructors; it’s still useful when cloning is the natural operation (templates, undo). The “when not to use” and the cost of hiding creation behind too many layers are underplayed.


Decision Framework

  • If you need to create one product and defer the concrete type → Factory Method (or DI).
  • If you have families of related products and want to swap the family → Abstract Factory.
  • If construction has many optional steps or complex validation → Builder; otherwise constructor or initialiser.
  • If cloning is the natural operation (templates, copy) → Prototype.
  • If you need exactly one instance app-wide and DI is not available → Singleton; otherwise prefer AddSingleton and inject.
  • If you’re not sure → Start with simple construction and DI; add a pattern when you have a clear creation problem.

You can also explore more patterns in the .NET Architecture resource page.

Key Takeaways

  • Factory Method for deferred creation; Abstract Factory for families of products; Builder for complex construction; Prototype for clone; Singleton sparingly, prefer DI.
  • Don’t use Abstract Factory for one family; don’t use Builder for simple DTOs; don’t use Singleton when scoped or transient would do.
  • Combine with DI so the container owns lifetime; avoid hiding dependencies inside creational types.

Summary

Creational patterns in .NET—Factory Method, Abstract Factory, Builder, Prototype, and Singleton—address how objects are created and who owns creation; use the right one so client code stays decoupled and testable. Picking the wrong pattern (e.g. Singleton where scoped DI would do) or over-engineering (Abstract Factory for a single product) leads to rigid or hard-to-test code; combining patterns with DI keeps lifetime and dependencies clear. Next, map creation hotspots in your codebase to one of the five patterns and apply incrementally with tests.


Need architectural guidance for real-world .NET platforms? I offer consulting for .NET architecture, API platforms, and enterprise system design.

When I Would Use This Again — and When I Wouldn’t

I would use creational patterns again when object creation is non-trivial (multiple representations, families, or controlled lifecycle) and I want testability or centralised construction. I wouldn’t use Factory for a single new in one place—then a constructor is enough. I also wouldn’t use Singleton (static) for services; use DI. Alternative: for simple DTOs or value objects, new and constructors are fine; introduce patterns when creation logic grows or when you need to swap implementations.


services
Frequently Asked Questions

Frequently Asked Questions

What is Factory Method?

Factory Method is a creational pattern where a method in a class (or base class) is responsible for creating one product; subclasses override that method to create concrete products. The client depends only on the creator type and product interface.

What is Abstract Factory?

Abstract Factory provides an interface for creating families of related products (e.g. Win UI or Mac UI). Concrete factories implement the interface and ensure all products come from the same family.

What is Builder?

Builder separates construction of a complex object from its representation. A builder exposes fluent methods for each step and a Build() method that returns the product. Use when you have many optional parameters or stepwise construction.

What is Prototype?

Prototype creates new objects by cloning an existing instance. Use when cloning is cheaper than new or when you need copies with small variations. Implement via ICloneable, copy constructor, or custom Clone(); be explicit about shallow vs deep.

What is Singleton?

Singleton ensures exactly one instance of a class. In .NET prefer AddSingleton<T> in DI so the same instance is shared and tests can replace it. Avoid static singleton when testability matters.

When use Factory Method vs Abstract Factory?

Factory Method: one product type; creation varies by subclass. Abstract Factory: families of related products; one factory produces all products in the family.

When use Builder vs constructor?

Use Builder when you have many optional parameters or stepwise construction and want readable, valid creation. Use a constructor when you have a few required parameters.

Deep vs shallow clone?

Shallow clone copies the object and its value-type fields but keeps references to nested objects—so nested state is shared. Deep clone duplicates nested objects too so the copy is fully independent.

Why avoid static Singleton?

Static singleton is hard to test (you cannot replace it with a mock) and ties the app to one concrete type. DI singleton (AddSingleton<T>) lets you inject an interface and replace it in tests.

services
Related Guides & Resources

services
Related services