Skip to content
Sanfor2004
← All articles
Design PatternsDesign PatternsCreational PatternsPythonC++Software Engineering

Builder (Creational Pattern)

By Ahmed (Sanfor) · 11 Sept 2026 · 5 min read
A sandwich moves through successive preparation stations, with each step adding part of the finished result.

Start with the design patterns overview · Part 02 of 23 · Creational patterns

Construct a complex object step by step while allowing different final representations. This article follows the example in my 23 Design Patterns repository, connecting the problem, participating classes, Python and C++20 implementations, and the trade-offs that decide whether to use it.

The Problem

A request has an endpoint, a timeout and a retry option; positional arguments become hard to read as options grow.

Naive Solution

Request request{"/orders", 5, true}; // what does true mean?

Why It Becomes a Problem

The constructor works, but calls with several integers and booleans hide intent and make swapped arguments hard to notice.

The Idea

Keep construction state in RequestBuilder. Named methods collect choices; build checks required values and returns a Request by value.

Reading the illustration

A restaurant uses the same sequence—bread, filling, sauce, finish—but different cooks can produce different sandwiches from those steps.

In the repository example, the same design idea addresses this software problem: A request has an endpoint, a timeout and a retry option; positional arguments become hard to read as options grow.

Structure

Diagram · Python source · C++20 source

Builder sketch map: Client leads through RequestBuilder to Request.
Builder: trace the example from caller through the pattern boundary to its collaborator or result. The arrows show flow, not ownership. Open the full-size diagram.
Client  -->  RequestBuilder  -->  Request

Participants

RequestBuilder stores temporary choices and validates them. Request owns the finished values. The client chooses the order of optional steps.

Canonical roles in this example:

  • Product — The contract of an object returned by creation code. Here: Request.
  • fluent interface — An interface shaped to read as a chain of calls; it does not by itself imply Builder. Here: RequestBuilder.endpoint().timeout().retry().
  • constructor — The special operation that initializes a new class instance. Here: Request::Request.

Python Example

The complete Python source is shown first.

class Request:
    def __init__(self, endpoint, timeout=30, retry=False):
        self.endpoint = endpoint
        self.timeout = timeout
        self.retry = retry

    def describe(self):
        print(f"{self.endpoint} timeout={self.timeout} retry={self.retry}")


class RequestBuilder:
    def __init__(self):
        self.endpoint_value = ""
        self.timeout_value = 30
        self.retry_value = False

    def endpoint(self, value):
        self.endpoint_value = value
        return self

    def timeout(self, seconds):
        self.timeout_value = seconds
        return self

    def retry(self, enabled):
        self.retry_value = enabled
        return self

    def build(self):
        if not self.endpoint_value or self.timeout_value <= 0:
            raise ValueError("Invalid request")
        return Request(self.endpoint_value, self.timeout_value, self.retry_value)


def main():
    RequestBuilder().endpoint("/orders").timeout(5).retry(True).build().describe()
    try:
        RequestBuilder().build()
    except ValueError:
        print("Invalid request rejected")
    try:
        RequestBuilder().endpoint("/orders").timeout(0).build()
    except ValueError:
        print("Zero timeout rejected")


if __name__ == "__main__":
    main()

Python Output

/orders timeout=5 retry=True
Invalid request rejected
Zero timeout rejected

Code Walkthrough

RequestBuilder stores temporary choices and validates them. Request owns the finished values. The client chooses the order of optional steps.

Start at the final call in the Python example. Follow the middle role in the diagram and compare how the C++20 version handles the same responsibility.

C++20 Example

#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>

class Request {
    std::string endpoint_;
    int timeout_;
    bool retry_;
public:
    Request(std::string endpoint, int timeout, bool retry)
        : endpoint_(std::move(endpoint)), timeout_(timeout), retry_(retry) {}
    void describe() const {
        std::cout << endpoint_ << " timeout=" << timeout_ << " retry=" << retry_ << '\n';
    }
};
class RequestBuilder {
    std::string endpoint_;
    int timeout_ = 30;
    bool retry_ = false;
public:
    RequestBuilder& endpoint(std::string value) { endpoint_ = std::move(value); return *this; }
    RequestBuilder& timeout(int seconds) { timeout_ = seconds; return *this; }
    RequestBuilder& retry(bool enabled) { retry_ = enabled; return *this; }
    Request build() const {
        if (endpoint_.empty() || timeout_ <= 0) throw std::invalid_argument("Invalid request");
        return Request{endpoint_, timeout_, retry_};
    }
};
int main() {
    const auto request = RequestBuilder{}.endpoint("/orders").timeout(5).retry(true).build();
    request.describe();
    try { static_cast<void>(RequestBuilder{}.build()); }
    catch (const std::invalid_argument&) { std::cout << "Invalid request rejected\n"; }
}

C++20 Output

/orders timeout=5 retry=1
Invalid request rejected

When to Use

Use it for objects with many independent options or a meaningful validation boundary.

Use cases

HTTP request configuration and test fixture assembly fit; this example performs no network request.

When NOT to Use

Avoid it for two obvious constructor arguments; a small aggregate with named fields may be clearer.

Advantages

Call sites explain the choices, and invalid builder state can be rejected before producing a result.

Trade-offs

There is an extra type to maintain. This Request constructor remains public, so production invariants would also need constructor validation or restricted access.

Factory Method · Abstract Factory

Common Confusion

Factory Method chooses the concrete product inside an inherited workflow. Builder assembles the configuration of a result over several calls.

Terms to Remember

  • Builder — Assemble a configured object through named steps before producing the result.
  • Product — The contract of an object returned by creation code. Example: Request.
  • fluent interface — An interface shaped to read as a chain of calls; it does not by itself imply Builder. Example: RequestBuilder.endpoint().timeout().retry().
  • constructor — The special operation that initializes a new class instance. Example: Request::Request.

Interview Vocabulary

  • object creation — Choosing a concrete type and establishing an object’s initial values and lifetime.
  • separation of concerns — Keeping distinct kinds of responsibility apart so they can change independently.
  • single responsibility — Keep a module focused on one coherent reason to change.

Interview Question

Does a fluent interface automatically make something a Builder? Explain where construction ends.

Mini Challenge

Reject timeouts above 120 and demonstrate both the boundary value and the first rejected value.

Compare the two versions

Named Python arguments often make a Builder unnecessary. This example keeps separate construction steps to show the intent. build creates a fresh Request; as in C++, calling the public Request constructor directly bypasses Builder validation. A GoF Director is optional here, and the example builds one representation. The two examples express the same pattern responsibility; compare their setup and output before changing an input.

Check yourself

  1. What happens if a caller bypasses build and calls Request directly?
  2. When would the naive solution on this page be easier to maintain? Give a concrete example.
  3. Change one input in the Python example. Predict the output and explain which responsibility handles the change.

Run and explore the example

The Python code comes from python/main.py, with expected output. The C++20 code comes from creational/builder/cpp/main.cpp. Follow the repository’s C++20 build instructions to compile it and compare the result with expected.txt. The output demonstrates this example’s behavior; it does not cover every input or the challenge above.

The source example and adapted explanation are © 2026 Sanfor2004, provided under the MIT license. The sketchbook cover is an illustration preserved from this site’s original pattern lessons.

Continue the series

Ahmed (Sanfor)Backend and automation engineer in Alexandria. I build the systems businesses run on, and paint between deploys.

Keep reading

Design PatternsAbstract Factory (Creational Pattern)5 minDesign PatternsAdapter (Structural Pattern)4 minDesign PatternsBridge (Structural Pattern)5 min

Have manual work eating your week?

Book a free call