Builder Design Pattern in .NET Core API
A beginner-friendly introduction to the Builder pattern in C# with a simple example, common builder variations, validation tips, and UML.

Intro
Builder is a creational design pattern used to create complex objects step by step. It helps you avoid large constructors with many parameters and keeps object creation readable.
What Is the Builder Pattern?
The Builder pattern separates object construction from the final object representation.
In simple terms:
- You call small methods to set parts of an object.
- You call
Build()at the end to get the final object. - You can create different object variations using the same builder.
Builder Pattern Example
Suppose we want to build a House object with optional fields.
public class House{ public string RoofType { get; set; } = string.Empty; public int Rooms { get; set; } public bool HasGarage { get; set; }}
public class HouseBuilder{ private readonly House _house = new();
public HouseBuilder SetRoof(string roofType) { _house.RoofType = roofType; return this; }
public HouseBuilder SetRooms(int rooms) { _house.Rooms = rooms; return this; }
public HouseBuilder AddGarage() { _house.HasGarage = true; return this; }
public House Build() => _house;}Usage
var house = new HouseBuilder() .SetRoof("Concrete") .SetRooms(3) .AddGarage() .Build();Why This Works
- Each builder method focuses on one part of object creation, so the calling code reads like a set of construction steps.
- The partially constructed object stays inside the builder until
Build()is called, which keeps creation logic in one place. - Optional values can be added only when needed, without creating many overloaded constructors.
- The same builder structure can produce different final results by changing the order or combination of steps.
When to Use It
- When object construction is complex and involves many optional steps or configurations.
- When you want to avoid constructor telescoping (many overloaded constructors).
- When you want cleaner and more readable creation code.
- When you want to reuse the same construction process for different object variants.
Builder is most useful when creation itself has structure. If the object only needs two or three fixed values, a normal constructor or object initializer is usually enough.
Important Note for ASP.NET Core
In ASP.NET Core, Builder is common in framework configuration APIs even if you do not explicitly create your own builder class. The platform already uses this pattern in places like WebApplicationBuilder, options setup, and middleware registration.
You may also register your own builder-related services through dependency injection when object construction needs to stay reusable and testable.
builder.Services.AddTransient<HouseBuilder>();That approach is helpful when the builder depends on other services, but in many cases a builder can remain a simple class created directly by the caller.
Core Components of the Pattern
| Part | Purpose | Example in this article |
|---|---|---|
| Builder | Step-by-step construction | HouseBuilder |
| Product | The object being built | House |
| Client | Calls builder methods | Usage code above |
Common Builder Variations
You will usually see the Builder pattern implemented in these three forms:
01-FluentBuilder
This subtype returns the builder instance from each method, so you can chain calls in a readable flow.
Why it matters in implementation:
- Clean and concise object construction code.
- Easy to add optional steps without changing caller style.
- Great fit for APIs that build one object from many optional values.
02-StepBuilder
This subtype enforces the order of build steps using interfaces. The caller must follow the defined sequence.
Why it matters in implementation:
- Prevents invalid object states at compile time.
- Useful when some steps are mandatory.
- Improves correctness in complex construction flows.
03-DirectorBasedBuilder
This subtype uses a Director class to control and reuse construction sequences for common object variants.
Why it matters in implementation:
- Reuses standard build recipes across the app.
- Keeps repeated construction flow out of controllers/services.
- Helps when you need multiple predefined object configurations.
How the Variations Differ
| Variation | Main idea | Best use case | Trade-off |
|---|---|---|---|
| Fluent Builder | Returns the same builder from each method | Clean, readable construction with optional steps | Can still allow invalid step order |
| Step Builder | Uses interfaces to enforce the build sequence | Required steps and compile-time safety | More interfaces and more setup |
| Director-Based Builder | Moves standard build flows into a director | Repeated object recipes across the app | Adds an extra class for orchestration |
The main difference is how much control you want over the construction flow. Fluent Builder focuses on readability, Step Builder focuses on correctness, and Director-Based Builder focuses on reuse of common build sequences.
SOLID Principles Behind It
SRP — Single Responsibility Principle
The builder handles construction logic, while the product class focuses on representing the final object. That keeps creation details out of the domain model.
OCP — Open/Closed Principle
You can extend the object creation process by adding new builder methods or a new concrete builder without changing the calling code pattern.
ISP — Interface Segregation Principle
In a Step Builder, each interface exposes only the next required action. Callers do not depend on methods they should not use yet.
DIP — Dependency Inversion Principle
The client can depend on an abstraction such as an IHouseBuilder interface instead of a concrete builder, which keeps the creation process easier to swap or test.
Advantages and Disadvantages
Advantages
- Simplifies building complex objects.
- Keeps code readable and clean.
- Reduces constructor complexity.
- Makes optional fields easier to manage.
- Separates construction logic from the final object.
- Makes repeated creation flows easier to test and reuse.
Disadvantages
- Can introduce extra classes and interfaces if overused.
- May be overkill for simple objects.
- A poorly designed builder can allow incomplete or invalid object states.
- Step builders and directors add more structure, which increases code volume.
UML Diagram
classDiagram class Client class Product class Builder { +Build() Product } class ConcreteBuilder
ConcreteBuilder --|> Builder Builder --> Product Client --> BuilderA Quick Usage Example
var familyHouse = new HouseBuilder() .SetRoof("Concrete") .SetRooms(4) .AddGarage() .Build();
var smallHouse = new HouseBuilder() .SetRoof("Tiles") .SetRooms(2) .Build();Both objects are created through the same builder, but each uses a different combination of steps. That is where Builder becomes practical.
How to Validate the Pattern
If you want to confirm your Builder implementation is working correctly, check these points:
- the final object should contain the values set through the builder methods
- optional steps should affect only the fields they are responsible for
- the builder should produce valid objects even when optional steps are skipped
- repeated builds should not accidentally leak state between different objects unless that behavior is intentional
A simple practical check is to build two different objects and verify that each one contains only the values that were configured for it.
Here is a small xUnit example:
[Fact]public void HouseBuilder_Should_Create_House_With_Configured_Values(){ var house = new HouseBuilder() .SetRoof("Concrete") .SetRooms(3) .AddGarage() .Build();
Assert.Equal("Concrete", house.RoofType); Assert.Equal(3, house.Rooms); Assert.True(house.HasGarage);}If you implement a Step Builder, validation should also confirm that required steps cannot be skipped. If you use a Director, test that each predefined build recipe returns the expected result.
Summary
The Builder pattern is useful when object creation has many steps or optional values. It improves readability, keeps constructors simple, and helps you create objects in a clean, maintainable way.




