In one sentence: Builder replaces one monstrous constructor with a series of named, optional steps, so you only specify the parts you care about and get a finished object at the end.
Real-world analogy
A general contractor runs every job by the same sequence: pour the foundation, raise the frame, fit the roof, hand over the keys. Same four calls, same order, every time. The contractor owns the sequence and touches no tools.
The crew on site decides what those calls mean. Hire the timber crew and the frame goes up in wood. Hire the brick crew and the identical four instructions produce a brick house. The contractor never learns which — the instruction is "raise the frame," not "raise a timber frame."
And the house stays on the crew's site until it's finished. There is no point where a roofless shell gets handed over "for now." At the end you ask the crew for the keys, and only then does a complete house become yours.
Mapping the analogy to the pattern:
- The contractor's fixed sequence → the Director, which runs the construction process by calling steps in order and names no product class.
- "Pour the foundation / raise the frame / fit the roof" → the Builder interface: one operation per part of the product.
- The crew you hired → the ConcreteBuilder; it knows the material, keeps the half-built house on its own site, and is the only participant that ever touches the Product.
- Asking the crew for the keys at the end →
GetResult()— the product is retrieved from the builder, not returned by each step, which is why nothing incomplete ever escapes. - A timber house and a brick house from the same four instructions → the same construction process producing different representations. Note you never asked "what kind of house is this?" — you hired the timber crew, so you already know.
Another angle: ordering at a deli counter. "Rye bread… turkey… no onions… extra mustard" — one named step at a time, skipping what you don't want, and the sandwich only reaches you once it's wrapped.
The problem
An HttpRequest has a URL, a method, headers, a body, a timeout, retry settings, a proxy. Two common approaches both fail:
- The telescoping constructor.
HttpRequest(url, "POST", headers, body, 30, 3, "")— nobody can read that. What is3? What does the empty string mean? Add one option and every existing call site breaks. - The default-construct-then-assign approach. Now the object exists in a half-built, invalid state, and nothing stops you from forgetting a required field.
Most fields are optional, most requests set three of them, and the combinations multiply.
The solution
Move the assembly into a separate builder object:
- The builder holds a work-in-progress object and exposes one method per step, each with a meaningful name.
- Every step returns
*this, so calls can be chained — this is the fluent style, and it is the idiomatic modern C++ form. - A final
build()returns the finished product. Nothing incomplete ever escapes.
The reader sees exactly which options you set, in any order, and the ones you skip take their defaults.
That is the everyday C++ shape of the pattern, and it is worth knowing that GoF draws it with one more part. In the book, a director owns the sequence of steps and a builder owns what each step actually produces. GoF's example is a reader for RTF files: the reader walks the document and calls "convert a character", "convert a paragraph" — and depending on which converter you hand it, the same walk yields plain text, TeX, or an editable text widget. That is the pattern's real intent: the same construction process, different representations. In the fluent form below the director has simply collapsed into the calling code — you are your own director.
Note: GoF points out something surprising in the implementation notes — there is deliberately no abstract Product class. The things different builders produce (a text file, a widget) usually have nothing in common worth abstracting, and the client, having chosen the concrete builder, already knows what it is getting back. GoF also suggests the builder's step methods be empty virtual functions rather than pure virtual ones, so a concrete builder overrides only the parts it cares about.
Structure
Four participants, and the arrow that matters most is the dashed one: only the ConcreteBuilder ever touches the Product, which is why swapping builders swaps representations.
- Builder — the abstract interface for creating the parts of a product, one operation per part.
- ConcreteBuilder — assembles the parts, keeps track of the representation it is producing, and offers a way to retrieve the finished product.
HttpRequestBuilderbelow, whosebuild()is that retrieval step. - Director — runs the construction process, calling the builder's steps in order. In the example below there is no separate director class: the chained call at each call site plays that role.
- Product — the complex object being assembled.
HttpRequest. Note it is retrieved from the builder rather than returned by each step.
C++ example
A fluent builder for HTTP requests.
#include <iostream>
#include <string>
#include <utility>
#include <vector>
class HttpRequest {
public:
std::string method = "GET";
std::string url;
std::vector<std::string> headers;
std::string body;
int timeoutSeconds = 30;
void send() const {
std::cout << method << " " << url << "\n";
for (const auto& h : headers)
std::cout << " " << h << "\n";
if (!body.empty())
std::cout << " body: " << body << "\n";
std::cout << " timeout: " << timeoutSeconds << "s\n";
}
};
class HttpRequestBuilder {
public:
explicit HttpRequestBuilder(std::string url) { request_.url = std::move(url); }
HttpRequestBuilder& method(std::string verb) {
request_.method = std::move(verb);
return *this; // chaining happens here
}
HttpRequestBuilder& header(const std::string& key, const std::string& value) {
request_.headers.push_back(key + ": " + value);
return *this;
}
HttpRequestBuilder& body(std::string content) {
request_.body = std::move(content);
return *this;
}
HttpRequestBuilder& timeout(int seconds) {
request_.timeoutSeconds = seconds;
return *this;
}
HttpRequest build() const { return request_; }
private:
HttpRequest request_;
};
int main() {
// Same builder, minimal representation.
HttpRequestBuilder("https://api.example.com/health").build().send();
std::cout << "\n";
// Same builder, fully specified representation.
HttpRequestBuilder("https://api.example.com/orders")
.method("POST")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer t0ken")
.body(R"({"item":"lamp","qty":2})")
.timeout(5)
.build()
.send();
}GET https://api.example.com/health
timeout: 30s
POST https://api.example.com/orders
Content-Type: application/json
Authorization: Bearer t0ken
body: {"item":"lamp","qty":2}
timeout: 5sThe two call sites ran different step sequences against the same builder and neither had to pass a placeholder for an option it didn't use. Swap in a builder that writes a curl command instead of an HttpRequest and those same sequences would produce a different representation entirely — that is the axis GoF cares about.
When should you use it?
GoF's applicability list is only two bullets, and both are about separation. Use Builder when:
- The algorithm for assembling a complex object should be independent of what the parts are and how they get put together. The code that decides the order of steps should not know the classes those steps produce.
- The construction process must be able to produce different representations. The same recipe run against different builders yields HTML, JSON, or a row in a database.
In modern C++ the pattern is also reached for on narrower, non-GoF grounds worth being honest about:
- A constructor has grown past four parameters, or several are optional with sensible defaults.
- Call sites are full of
"",0,nullptrplaceholders whose meaning you have to look up.
Pros and cons
Pros (GoF's consequences)
- You can vary the product's internal representation. The builder interface hides what the product is made of, so producing a different representation means writing a new builder — nothing else changes.
- It isolates construction from representation. Each concrete builder holds all the code for assembling one kind of product, written once, and different directors can reuse it.
- It gives finer control over construction. Unlike creational patterns that produce an object in a single shot, the product is built step by step and only handed over when it is finished, so no half-built object escapes.
Cons
- One extra class to write and keep in sync with the product's fields.
- Nothing in the type system forces you to call
build(), or to set a genuinely required field. - For a struct with three obvious fields, aggregate initialisation or designated initialisers are simpler.
Tip: If your builder is used once and immediately discarded, add an rvalue-qualified
HttpRequest build() && { return std::move(request_); }so the finished object is moved out rather than copied.
How to remember it
Memory hook: "Order at the deli counter." Step, step, step — and the sandwich only reaches you once it's wrapped. That last part is the sibling test: Builder is step-by-step with the product ready at the end; Abstract Factory hands back a product on every single call.
Related patterns
- Abstract Factory — also builds complex objects, but emphasises families and returns each product immediately; Builder concentrates on one product assembled step by step and returned last.
- Composite — GoF's note that a composite is what a builder often builds; a tree assembled node by node is the classic case.
- Factory Method — a practical way to let a builder decide which concrete parts it assembles.