Some design patterns need long explanations. Builder isn't one of them — you've been using it every time you order a sandwich at Subway.
The problem: the mystery constructor
You want to create a sandwich object. With a plain constructor, ordering looks like this:
var sandwich = new Sandwich("wheat", "chicken", true, false, true, "mayo", null, true);
Quick — what does true, false, true mean? Lettuce? Onions? Toasted? Nobody knows without opening the class. And if you only want bread and meat, you're still forced to pass something for every other slot.
This is the telescoping constructor problem: too many parameters, half optional, impossible to read. It's the signal to reach for Builder.
How Subway actually works
You walk down the counter and build your order step by step:
"Wheat bread... chicken... add lettuce... no onions... extra mayo... okay done, wrap it up!"
Each step is named. You skip what you don't want. At the end you say "done" and receive the finished sandwich.
That's the Builder pattern. Here's the same order in C#:
var sandwich = new SandwichBuilder()
.Bread("wheat")
.Meat("chicken")
.AddLettuce()
.ExtraMayo()
.Build(); // ← "wrap it up!"
Read it out loud. It reads like the actual Subway order.
What's inside? (No magic, again)
public class SandwichBuilder
{
private string _bread;
private string _meat;
private bool _lettuce;
private bool _extraMayo;
public SandwichBuilder Bread(string b) { _bread = b; return this; }
public SandwichBuilder Meat(string m) { _meat = m; return this; }
public SandwichBuilder AddLettuce() { _lettuce = true; return this; }
public SandwichBuilder ExtraMayo() { _extraMayo = true; return this; }
public Sandwich Build() => new Sandwich(_bread, _meat, _lettuce, _extraMayo);
}
Two things to notice:
1. Every method returns this. That's the trick that makes chaining possible. Each call hands the builder back to you so you can keep adding toppings. If a method returned void, the chain would break right there. This style is called a fluent interface.
2. The real constructor is called exactly once — inside Build(). You never touch it. You talk to the builder; the builder talks to the constructor. Just like Subway: you never touch the oven, the staff does.
And skipping is free. A plain order just calls fewer methods:
var plain = new SandwichBuilder().Bread("white").Meat("tuna").Build();
No nulls. No false, false, true. Skip = don't call.
You already use this every day
Every ASP.NET Core app starts with a builder — it's even named that:
var builder = WebApplication.CreateBuilder(args); // the counter
builder.Services.AddControllers(); // add toppings
builder.Services.AddSingleton<IConfig, Config>(); // more toppings
var app = builder.Build(); // wrap it up!
StringBuilder, ConfigurationBuilder, HostBuilder — .NET is full of this pattern.
"But don't I have to write a method for every option?"
Yes — and that's fine, for two reasons.
First, it's not extra logic, just relocation. Those options exist anyway as constructor parameters. The builder trades mystery parameters for named methods. Same count, far more readable.
Second, real builders are small — 5 to 15 methods. If a class genuinely needs 100 options, the class is too fat; that's a design smell, not a builder problem.
For simple cases, C# even gives you a mini-builder for free — the object initializer:
var sandwich = new Sandwich { Bread = "wheat", Meat = "chicken", Lettuce = true };
Write a full builder when you need validation before creation, step-by-step construction logic, or an immutable final object.
Builder vs Factory — don't mix them up
- Factory = "give me a biryani" → one call, the kitchen picks which dish
-
Builder = "let ME customize step by step" → many calls, then
.Build()
Factory answers "which one?" — Builder answers "with what options?"
One line to remember
Builder = the Subway counter. Choose step by step, skip what you don't want, say "done", get your object.
Part 3 of a series on must-know design patterns in C#, explained the simple way. Part 1: Singleton. Part 2: Factory Method. Next up: Adapter — the travel plug pattern.
Top comments (0)