Skip to main content
CleanCodeMastery

Builder Pattern: Stitch It Step by Step, Like a Master Tailor

Learn the Builder design pattern with an Indian tailor shop story, fluent TypeScript and C# code, the telescoping constructor problem, and practice tasks.

24 min read Updated June 11, 2026beginner
builderdesign patternscreationaltypescriptfluent interfaceimmutability

๐ŸŽฏ The story of Iqbal Chacha's tailor shop

Come, let us visit a small tailoring shop in Lucknow.

Iqbal Chacha is a master tailor. His shop, Noor Tailors, is famous for kurtas. His grandson Aarif, a college student, helps at the counter on weekends. Now, here is the interesting thing: no two customers want the same kurta. Farhan bhai wants plain white cotton for daily wear. Mr. Joshi wants silk with chikankari embroidery for his son's wedding. The principal of the school nearby, Mrs. D'Souza, orders two hundred uniform kurtas, all identical.

How does Iqbal Chacha take an order? Does he ask the customer to shout out every detail in one breath, in a fixed order โ€” "cotton-white-size40-bandcollar-fullsleeves-twopockets-woodenbuttons-noembroidery!"? Of course not. Nobody can order like that, and nobody could check such an order for mistakes.

Instead, he keeps a simple order slip. He fills it step by step, in a friendly conversation:

  • "Which fabric?" โ€” Cotton.
  • "Colour?" โ€” White.
  • "Size?" โ€” 40.
  • "Collar?" โ€” Band collar.
  • "Pockets?" โ€” One, on the left.
  • "Embroidery?" โ€” No, plain.

Some questions can be skipped โ€” then Chacha uses sensible defaults (full sleeves, standard buttons). The order can be given in any sequence. And only at the end, when the customer says "bas, that is all", Chacha reads the slip once, checks that nothing important is missing โ€” fabric and size are compulsory! โ€” and then stitches the final kurta. Once stitched, the kurta does not change. If you want something different, you place a new order.

One more clever thing: for Mrs. D'Souza's uniform order, Chacha does not ask two hundred questions. He has a ready-made recipe card: "School Uniform = white cotton, band collar, half sleeves, one pocket." He just replays the card two hundred times.

Figure 1: One order at Noor Tailors โ€” slow questions first, careful check, then stitching

The order slip is a Builder. The recipe card is a Director. The stitched kurta is the Product. This whole shop is the Builder pattern. Keep Chacha, Aarif, Farhan bhai, and Mrs. D'Souza in your mind โ€” they will return in every section. Let us now learn the pattern properly.

๐Ÿ’ก What is the Builder pattern?

Builder is a creational design pattern for constructing complex objects step by step, instead of cramming the whole configuration into one giant constructor call.

Here is the plain idea. Some objects have many parts: a few required, many optional. The Builder pattern moves the construction work out of the product class and into a separate builder object. You call small, well-named step methods โ€” withFabric(), withSize(), addPocket() โ€” only for the parts you care about. At the end, you call one final method, usually build(). That method validates everything collected so far and returns the finished product, often as an immutable object (one that can never change after creation). Just like Chacha's kurta: questions in any order, the slip checked once, the stitched kurta final.

The pattern appears in the Gang of Four book (1994), but its most loved modern form is the fluent builder: every step method returns the builder itself (return this), so the calls chain together beautifully:

const kurta = new KurtaBuilder()
  .withFabric("cotton")
  .withColor("white")
  .withSize(40)
  .build();

Read that aloud. It is almost an English sentence. That readability is the pattern's superpower.

Figure 2: The whole Builder idea on one mind map
๐Ÿ’ก

One-line summary: Builder = fill an order slip step by step with named methods, then call build() once to receive the finished, checked, unchangeable product.

College corner: notice the separation of mutable construction state from immutable result. The builder is deliberately mutable and short-lived; the product is immutable and long-lived. This split gives you thread-safety for free on the product side (immutable objects can be shared across threads without locks) while keeping the construction ergonomic. Many functional languages bake this in โ€” for example, Rust's ownership system makes the "consume the builder, emit the product" handoff explicit.

โš ๏ธ The problem it solves

Let us feel the pain first. Suppose Aarif, fresh from one semester of programming, codes the shop without a builder. A kurta with many options pushes him toward the famous telescoping constructor anti-pattern โ€” a ladder of ever-longer constructors:

// BAD CODE: the telescoping constructor anti-pattern
class Kurta {
  constructor(
    fabric: string,
    size: number,
    color?: string,
    collar?: string,
    sleeves?: string,
    pockets?: number,
    buttons?: string,
    embroidery?: string | null,
    giftWrap?: boolean
  ) {
    // ...store everything...
  }
}
 
// And now look at the call site. Can YOU read this?
const order = new Kurta(
  "silk", 42, "maroon", undefined, "full", 0, "fancy", "chikankari", true
);

Be honest โ€” what does that 0 mean? Why is there an undefined in the middle? What is true at the end? Even Aarif, who wrote it, forgets in a week. When Mr. Joshi calls to confirm his wedding order, nobody in the shop can read it back to him. The troubles pile up:

  1. Unreadable call sites. A wall of positional values with no names. One wrong position โ€” say, swapping pockets and size โ€” and you get a size-0 kurta with 42 pockets. The compiler may not even complain.
  2. Combinatorial explosion. To "help", people write many constructor overloads: (fabric, size), (fabric, size, color), (fabric, size, color, collar)... Every new optional field multiplies the variations.
  3. Half-built objects. The other escape route โ€” make everything a public setter โ€” is worse. Now the object can exist with fabric set but size missing, and some code may use it in that broken, half-stitched state. Imagine Chacha delivering a kurta with one sleeve.
  4. No place for whole-object validation. Rules like "embroidery needs silk or cotton, not polyester" have no natural home when fields are set one by one from outside.
Figure 3: The two bad roads โ€” telescoping constructors or exposed half-built objects โ€” and what each one costs

And which of these troubles actually bites teams the most? Ask anyone who has maintained a codebase full of eight-argument constructors:

Figure 4: Where constructor-related pain actually comes from in real projects

We want construction that is readable (named steps), flexible (any order, skip optionals), safe (required fields checked once, in one place), and that can produce an immutable final object. The Builder pattern gives us all four โ€” it is Chacha's order slip, typed out.

๐Ÿ› ๏ธ How Builder works, step by step

Let us set up Noor Tailors properly. Follow these steps:

  1. List the product's fields. Write down everything a kurta can have. Mark which fields are required (fabric, size) and which are optional with defaults (color = white, sleeves = full, pockets = 0...). This is Chacha deciding which questions are compulsory.
  2. Create the Builder class. It holds the same fields as mutable working state โ€” this is the order slip. Seed the optional fields with their defaults.
  3. Write one step method per logical part. withFabric(), withColor(), withCollar(), addPocket(), withEmbroidery(). In the fluent style, every step ends with return this so the calls can chain.
  4. Write build(). It checks required fields, enforces cross-field rules (the "whole-object validation"), then constructs and returns the final Kurta. The product's constructor can be hidden so the builder is the only door in.
  5. (Optional) Add a Director. If the same step sequence repeats โ€” school uniform, wedding special โ€” wrap each sequence in a named method of a small recipes class. These are Chacha's recipe cards.
Figure 5: The participants โ€” the Director knows recipes, the Builder knows steps, the Product is the result

And here is Farhan bhai's order as a conversation in time. Watch how every step quietly returns the builder itself, and how the kurta appears only at the very end:

Figure 6: One custom order from first step to finished kurta
โ„น๏ธ

In the classic GoF version there is also a Builder interface with several concrete builders, so the same recipe can produce totally different representations (for example, one builder makes a real Car, another makes the car's instruction Manual). In everyday modern code, the single fluent builder you see here is by far the most common form.

College corner: the GoF multi-builder form is worth one minute of thought. A single Director recipe โ€” "add wheels, add engine, add seats" โ€” replayed against a CarBuilder yields a car, and against a ManualBuilder yields documentation describing the same steps. The construction process and the final representation become independent axes of variation. Modern compilers use exactly this split: one parsing process, different builders producing an AST, bytecode, or pretty-printed source.

๐Ÿงช Real-life code example

Time to code Noor Tailors in TypeScript, section by section.

Section 1: the Product. The kurta is immutable โ€” all fields are readonly, set once in the constructor.

// ---------- PRODUCT ----------
// Immutable: once stitched, a kurta never changes.
class Kurta {
  constructor(
    public readonly fabric: string,
    public readonly color: string,
    public readonly size: number,
    public readonly collar: string,
    public readonly sleeves: string,
    public readonly pockets: number,
    public readonly buttons: string,
    public readonly embroidery: string | null
  ) {}
 
  describe(): string {
    const extra = this.embroidery
      ? ` with ${this.embroidery} embroidery`
      : "";
    return (
      `A size-${this.size} ${this.color} ${this.fabric} kurta, ` +
      `${this.collar} collar, ${this.sleeves} sleeves, ` +
      `${this.pockets} pocket(s), ${this.buttons} buttons${extra}.`
    );
  }
}

Yes, this constructor is long โ€” but notice that only the builder will ever call it. No human has to read or write that call directly. The mess is locked inside one room, like the cutting table at the back of Chacha's shop that customers never see.

Section 2: the Builder. This is Iqbal Chacha's order slip.

// ---------- BUILDER ----------
class KurtaBuilder {
  // Required fields: no defaults; must be set before build().
  private fabric: string | null = null;
  private size: number | null = null;
 
  // Optional fields: sensible defaults, like Chacha assumes.
  private color = "white";
  private collar = "band";
  private sleeves = "full";
  private pockets = 0;
  private buttons = "standard";
  private embroidery: string | null = null;
 
  withFabric(fabric: string): KurtaBuilder {
    this.fabric = fabric;
    return this; // fluent: enables chaining
  }
 
  withSize(size: number): KurtaBuilder {
    this.size = size;
    return this;
  }
 
  withColor(color: string): KurtaBuilder {
    this.color = color;
    return this;
  }
 
  withCollar(collar: string): KurtaBuilder {
    this.collar = collar;
    return this;
  }
 
  withSleeves(sleeves: "full" | "half"): KurtaBuilder {
    this.sleeves = sleeves;
    return this;
  }
 
  addPocket(): KurtaBuilder {
    this.pockets += 1;
    return this;
  }
 
  withButtons(buttons: string): KurtaBuilder {
    this.buttons = buttons;
    return this;
  }
 
  withEmbroidery(style: string): KurtaBuilder {
    this.embroidery = style;
    return this;
  }
 
  // The final step: validate everything, then stitch.
  build(): Kurta {
    if (this.fabric === null) {
      throw new Error("Order incomplete: fabric is required.");
    }
    if (this.size === null) {
      throw new Error("Order incomplete: size is required.");
    }
    // A whole-object rule, checked in ONE place:
    if (this.embroidery !== null && this.fabric === "polyester") {
      throw new Error("Embroidery needs cotton or silk, not polyester.");
    }
 
    return new Kurta(
      this.fabric,
      this.color,
      this.size,
      this.collar,
      this.sleeves,
      this.pockets,
      this.buttons,
      this.embroidery
    );
  }
}

Three details deserve a careful look:

  • Required vs optional is crystal clear. Required fields start as null; optional fields start with defaults. The structure of the class documents the rules, like the compulsory boxes on Chacha's slip.
  • Every step returns this. That single line turns plain methods into a chainable, sentence-like API.
  • build() is the gatekeeper. Field checks and cross-field rules live together in one place, and an invalid kurta can never escape into the program. Either you get a complete, valid, immutable kurta โ€” or a clear error at the counter.

Section 3: the Director (optional but lovely). Chacha's recipe cards.

// ---------- DIRECTOR ----------
// Named recipes for orders that repeat again and again.
class TailorRecipes {
  schoolUniform(size: number): Kurta {
    return new KurtaBuilder()
      .withFabric("cotton")
      .withColor("white")
      .withSize(size)
      .withSleeves("half")
      .addPocket()
      .build();
  }
 
  weddingSpecial(size: number): Kurta {
    return new KurtaBuilder()
      .withFabric("silk")
      .withColor("maroon")
      .withSize(size)
      .withCollar("sherwani")
      .withButtons("fancy")
      .withEmbroidery("chikankari")
      .build();
  }
}

Section 4: the customers.

// ---------- CLIENT ----------
// Farhan bhai's custom order, built step by step, in any order:
const dailyWear = new KurtaBuilder()
  .withSize(38)
  .withFabric("cotton")
  .addPocket()
  .addPocket()
  .build();
console.log(dailyWear.describe());
 
// Mrs. D'Souza's uniforms and Mr. Joshi's wedding kurta use recipe cards:
const recipes = new TailorRecipes();
const uniform = recipes.schoolUniform(36);
const groomKurta = recipes.weddingSpecial(42);
console.log(uniform.describe());
console.log(groomKurta.describe());
 
// And a careless order is caught at the counter, not at the wedding:
try {
  new KurtaBuilder().withFabric("polyester").withSize(40)
    .withEmbroidery("chikankari").build();
} catch (e) {
  console.log("Rejected: " + (e as Error).message);
}

The output:

A size-38 white cotton kurta, band collar, full sleeves, 2 pocket(s), standard buttons.
A size-36 white cotton kurta, band collar, half sleeves, 1 pocket(s), standard buttons.
A size-42 maroon silk kurta, sherwani collar, full sleeves, 0 pocket(s), fancy buttons with chikankari embroidery.
Rejected: Embroidery needs cotton or silk, not polyester.

Look at what we gained. Farhan bhai's order set its steps in a different sequence (size before fabric) โ€” no problem. The uniform and wedding orders reused recipes โ€” no copy-paste. And the bad polyester order was stopped at build time, with a clear message, before any invalid kurta entered the program. Dadi ji from the Abstract Factory post would approve: catch mistakes at the counter, not at the wedding.

๐Ÿ“Š The same idea in C#

C# developers meet builders every single day. Here is our tailor shop, shortened:

public sealed record Kurta(
    string Fabric, string Color, int Size,
    string Collar, int Pockets, string? Embroidery);
 
public class KurtaBuilder
{
    private string? _fabric;
    private int? _size;
    private string _color = "white";
    private string _collar = "band";
    private int _pockets = 0;
    private string? _embroidery;
 
    public KurtaBuilder WithFabric(string fabric)
    {
        _fabric = fabric;
        return this;
    }
 
    public KurtaBuilder WithSize(int size)
    {
        _size = size;
        return this;
    }
 
    public KurtaBuilder WithColor(string color)
    {
        _color = color;
        return this;
    }
 
    public KurtaBuilder AddPocket()
    {
        _pockets++;
        return this;
    }
 
    public KurtaBuilder WithEmbroidery(string style)
    {
        _embroidery = style;
        return this;
    }
 
    public Kurta Build()
    {
        if (_fabric is null) throw new InvalidOperationException("Fabric is required.");
        if (_size is null) throw new InvalidOperationException("Size is required.");
        return new Kurta(_fabric, _color, _size.Value, _collar, _pockets, _embroidery);
    }
}
 
// Usage โ€” reads like a sentence:
var kurta = new KurtaBuilder()
    .WithFabric("silk")
    .WithColor("maroon")
    .WithSize(42)
    .WithEmbroidery("chikankari")
    .Build();

The record even gives us immutability for free. Same shop, same idea, different language.

And one small Python variation that testers adore โ€” the test data builder. Aarif uses this trick in his college project: tests need many slightly-different kurtas, and the builder gives each test a readable starting point with defaults for everything it does not care about:

class KurtaBuilder:
    def __init__(self):
        self._fabric = "cotton"   # test default: every field pre-filled
        self._size = 40
        self._color = "white"
        self._pockets = 0
 
    def with_fabric(self, fabric):
        self._fabric = fabric
        return self
 
    def with_size(self, size):
        self._size = size
        return self
 
    def add_pocket(self):
        self._pockets += 1
        return self
 
    def build(self):
        return Kurta(self._fabric, self._color, self._size, self._pockets)
 
 
def a_kurta():
    return KurtaBuilder()   # tiny readable entry point
 
 
# In tests: each test names ONLY what it cares about.
tiny = a_kurta().with_size(28).build()
silk = a_kurta().with_fabric("silk").build()
pocketed = a_kurta().add_pocket().add_pocket().build()

Notice the twist: in test builders, even the "required" fields get defaults, because a test about pockets should not be forced to talk about fabric. The pattern bends to its purpose.

๐Ÿ’ก Where you see it in real software

The Builder pattern may be the most-used GoF pattern in daily programming. You have probably used it already without knowing its name:

  • StringBuilder in both C# and Java is the most famous builder of all. You call append() again and again โ€” step by step โ€” and finally call ToString() / toString() to receive the finished string. The final call is the build() step.
  • Java's HttpRequest.newBuilder() (since Java 11) constructs HTTP requests fluently: set the URI, add headers, choose the method, then .build(). The returned request is immutable โ€” exactly like our stitched kurta.
  • Lombok's @Builder annotation in Java generates a complete fluent builder for any class automatically, because the pattern is so common that Java developers wanted it as a one-word annotation. See the Project Lombok docs.
  • ASP.NET Core is practically built out of builders: WebApplication.CreateBuilder(args) collects services, configuration, and logging step by step before builder.Build() produces the application. ConfigurationBuilder and HostBuilder follow the same shape.
  • Joshua Bloch's Effective Java, Item 2 โ€” "Consider a builder when faced with many constructor parameters" โ€” is the most famous written argument for this pattern. It popularized the fluent, static-inner-class builder style that Java uses everywhere today.
  • Test data builders are a beloved testing trick, as you saw in the Python snippet: a_kurta().with_size(28).build() creates readable test objects with defaults for everything you do not care about.

For study material, see the worked builder examples in iluwatar/java-design-patterns, the compact version in faif/python-patterns, and the illustrated chapter on Refactoring Guru.

๐ŸŽฏ When to use it and when not to

First, the map. Noor Tailors sits firmly in the top-right: many optional fields, and validation that must look at the whole order.

Figure 7: Should your object get a builder? Find your spot on the map

Then confirm with the table:

SituationUse Builder?Why
A constructor needs many parameters, several optionalโœ… YesNamed steps replace unreadable positional arguments
You want the final object to be immutable but still configurableโœ… YesMutable builder collects values; build() freezes them
Validation must look at the whole object, not one field at a timeโœ… Yesbuild() is the single natural checkpoint
The same construction sequences repeat in many placesโœ… YesMove them into Director recipe methods
You assemble tree-shaped or composite structures piece by pieceโœ… YesEach step can attach one more part
The object has two or three simple fieldsโŒ NoA plain constructor or named/default arguments is shorter
Your language has named parameters with defaults and you need nothing moreโŒ NoTypeScript object parameters or Python keyword arguments may already solve it
You actually need to pick between different types of objectโŒ NoThat is a factory's job, not a builder's

What does the builder cost, and what does it pay back? Writing the builder is extra work up front โ€” all those step methods. But every call site afterwards becomes short, named, and safe. The more places construct the object, the faster the builder pays for itself:

Figure 8: Builder costs once, pays at every call site โ€” readability errors at call sites drop to nearly zero
โ„น๏ธ

Languages matter here. TypeScript and Python can often pass a single options object or keyword arguments with defaults, which solves the readability problem. The Builder still wins when you also need step-by-step assembly across time (collect values from several places before finishing), whole-object validation, or a guaranteed immutable product.

College corner: there is an advanced trick called the step builder (or staged builder) that encodes required fields in the type system. KurtaBuilder.withFabric() returns a NeedsSize type, whose only method is withSize(), which returns the full builder. Forgetting a required field then becomes a compile-time error instead of a runtime throw. It costs more interfaces, so teams reserve it for objects where a missing field is truly dangerous.

๐Ÿ› ๏ธ Lifecycle of one order slip

The builder itself has a life. It is born empty, collects steps, faces judgement in build(), and then its product lives on while the slip is thrown away. Watch the two outcomes of validation:

Figure 9: The life of one order slip โ€” collect steps, validate once, then the kurta is frozen forever

Two states deserve attention. The self-loop on Collecting is the "any order, any number of steps" freedom. And there is no arrow out of Stitched back to Collecting โ€” once the kurta exists, nobody can re-open it. If Kurta had public setters, that arrow would exist, and the whole safety story would collapse. When you design a builder, draw this diagram and check: can anything escape from Collecting without passing through Validating? If yes, you have a hole.

๐Ÿ“Š Compare with cousins

PatternQuestion it answersConstruction styleResult
Builder"How is this one complex object assembled?"Many small steps, then build()One complex (often immutable) object
Factory Method"Which single object should this step create?"One call, decided by a subclassOne object, returned immediately
Abstract Factory"Which matching family of objects do I need?"One call per family memberA set of related objects
Prototype"Can I copy an existing object instead?"One clone() callA duplicate to adjust

The cleanest way to remember the difference: factories choose, builders assemble. Sharma Aunty's branches chose a vehicle in one shot. Meena Tai's counters chose a whole matching family in one shot per item. Iqbal Chacha never chooses between kurta types at all โ€” he assembles one kurta, slowly, conversationally, and hands it over only when it is complete and checked.

These patterns also cooperate nicely. An Abstract Factory can hand out preconfigured builders (a counter that gives you order slips). A builder's build() can internally use a Factory Method to create parts (Chacha sources buttons from a button-maker). And many teams begin with a telescoping constructor, refactor to a Builder when it hurts, and later add a Director when recipes repeat.

โš ๏ธ Common mistakes students make

โš ๏ธ

Mistake 1: Forgetting return this. Write one step method without return this, and the chain breaks with a confusing "cannot read property of undefined" error at the next step. Check every step method ends the same way.

โš ๏ธ

Mistake 2: Doing validation in every step instead of in build(). Per-step checks cannot see the whole picture โ€” the embroidery-needs-silk rule involves two fields. Simple per-field checks are fine in steps, but whole-object rules belong in build(), where everything is finally known. Chacha checks the slip once, at the end, not after every question.

โš ๏ธ

Mistake 3: Reusing one builder for many products without resetting. After build(), the slip still holds the old values. Building again may silently carry over the previous customer's embroidery! Imagine Farhan bhai's plain kurta arriving with Mr. Joshi's chikankari. Either create a fresh builder per product, or give the builder a clear reset() and call it after every build.

๐Ÿšจ

Mistake 4: Leaving the product mutable anyway. If Kurta keeps public setters, anyone can "re-stitch" it after delivery, and the safety promised by build() validation is lost. Make the product's fields readonly and, where the language allows, hide its constructor so the builder is the only way in.

The same list as a quick-scan table:

MistakeWhat it looks likeThe fix
Missing return thisChain crashes at the next stepEvery step method returns the builder
Validation scattered in stepsCross-field rules half-checkedWhole-object rules live in build()
Reused builder, no resetPrevious order's values leakFresh builder per product, or reset()
Mutable productPublic setters on Kurtareadonly fields, hidden constructor

๐Ÿงช Quick revision box

+=================================================================+
|                 BUILDER - QUICK REVISION                        |
+=================================================================+
|  IDEA       : Assemble a complex object with small NAMED steps; |
|               build() validates and returns the final product.  |
|  POPULAR FORM: Fluent builder - every step returns this.        |
|-----------------------------------------------------------------|
|  PARTICIPANTS                                                   |
|    Product   -> Kurta (immutable once stitched)                 |
|    Builder   -> KurtaBuilder (the order slip)                   |
|    Director  -> TailorRecipes (optional recipe cards)           |
|-----------------------------------------------------------------|
|  GOLDEN RULES                                                   |
|    1. Required fields start null; optionals get defaults.       |
|    2. Whole-object validation lives in build(), one place.      |
|    3. Steps can run in ANY order; skip what you do not need.    |
|    4. Fresh builder per product, or reset() after build().      |
|-----------------------------------------------------------------|
|  KILLS      : the telescoping constructor anti-pattern          |
|  REAL LIFE  : StringBuilder, HttpRequest.newBuilder (Java 11),  |
|               Lombok @Builder, ASP.NET Core WebApplicationBuilder|
|  REMEMBER   : "Order slip first, stitching last."               |
+=================================================================+

๐ŸŽฏ Practice exercise

Pick up your needle and thread! Five tasks:

  1. Extend the kurta builder. Add a withLength("short" | "knee" | "long") step with default "knee", and a new rule in build(): a "short" kurta cannot have a "sherwani" collar. Write one order that gets rejected by this rule.
  2. Pizza order builder. Model a Pizza with required size, and optional base (default "thin"), cheese (default normal), and a list of toppings via addTopping(). Validate in build() that a pizza has at most 5 toppings. Then create a PizzaRecipes director with a margherita(size) recipe.
  3. Draw before you code. For exercise 2, draw your own versions of Figure 6 (sequence) and Figure 9 (state diagram). Check your diagram for the one rule that matters: nothing escapes Collecting without passing Validating.
  4. Think and write (no code). Your friend says, "TypeScript has optional object parameters โ€” new Kurta({ fabric: 'cotton', size: 40 }) โ€” so the Builder pattern is useless here." Write 4-5 sentences: which two problems does the options-object solve, and which three benefits of a real builder (think validation, immutability, step-by-step assembly over time) does it still miss?
  5. College corner challenge. Implement the step builder for the kurta: withFabric() returns a type whose only method is withSize(), which returns the full builder. Confirm that forgetting the size now fails at compile time, then write two sentences on whether the extra interfaces were worth it.

Shabash! You have now finished all three big creational patterns โ€” Sharma Aunty chose vehicles, Meena Tai guaranteed matching thalis, and Iqbal Chacha stitched step by step. Go back and revise the Factory Method and Abstract Factory posts, and notice how all three answer one deep question differently: who creates the object, and how?

Frequently asked questions

Why not just use a constructor with many parameters?
A long constructor call like new Kurta('cotton', 'white', 40, true, false, true, null) is impossible to read โ€” nobody remembers what the third true means. A builder replaces this with named steps like withFabric('cotton') and addPocket(), so the code explains itself.
What is a fluent builder?
A fluent builder is the popular style where every step method returns the builder itself. Because of this, you can chain calls one after another, like builder.withFabric('cotton').withSize(40).build(). It reads almost like an English sentence.
Do I always need a Director class?
No, the Director is optional. You only add one when you notice the same sequence of build steps repeating in many places. The Director stores these sequences as named recipes, like schoolUniform() or weddingSpecial(), so you do not copy-paste the steps.
When does build() throw an error?
The build() method is the right place to check that all required fields are set and the combination makes sense. For example, if no fabric was chosen, build() throws an error immediately. This way a half-finished, invalid object is never handed to the rest of the program.
How is Builder different from Factory Method?
A factory creates the object in one single call and focuses on which type to create. A builder assembles one complex object across many small steps and only hands it over at the end, focusing on how the object is put together. Factories answer 'which one?'; builders answer 'assembled how?'.

Further reading

Related Lessons