I. Builder - Creational Pattern
The Builder is a Creational Design Pattern that lets you construct complex objects step by step. Unlike other creational patterns, Builder doesn't require products to have a common interface. This makes it possible to reuse the same construction process to produce different representations of an object.
The pattern addresses the classic problem of telescoping constructors — when a class has too many optional fields, constructors quickly become unmanageable. Builder abstracts the initialization logic away from the caller, while ensuring that every concrete builder follows the same sequence of steps.
II. Real-world Example
Imagine a vehicle manufacturing facility. The factory needs to build different types of vehicles — bicycles and motorbikes — using the same production steps. Each step (setting wheels, seats, and structure) must be executed in a defined sequence, but the outcome differs depending on which builder is active.
This use case introduces the following components:
- VehicleProduct: The final assembled object. It holds three fields:
Wheels(int),Seats(int), andStructure(string).
- BuildProcess (interface): Defines the construction contract —
SetWheels(),SetSeats(),SetStructure(), andGetVehicle(). Each method returns the builder itself, enabling method chaining.
- BicycleBuilder: A concrete builder that produces a bicycle — 2 wheels, 1 seat, structure
"Bicycle".
- MotorbikeBuilder: A concrete builder that produces a motorbike — 2 wheels, 2 seats, structure
"Motorbike".
- ManufacturingDirector: The orchestrator. It holds a reference to a
BuildProcessand callsConstruct()to execute all steps in the correct order, regardless of which builder is set.
The class diagram for the above scenario:

III. Implementation
- Definition of the product:
package builder
type VehicleProduct struct {
Wheels int
Seats int
Structure string
}- Definition of the BuildProcess interface:
package builder
type BuildProcess interface {
SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}- BicycleBuilder — concrete builder for bicycles:
package builder
type BicycleBuilder struct {
v VehicleProduct
}
func (c *BicycleBuilder) SetWheels() BuildProcess {
c.v.Wheels = 2
return c
}
func (c *BicycleBuilder) SetSeats() BuildProcess {
c.v.Seats = 1
return c
}
func (c *BicycleBuilder) SetStructure() BuildProcess {
c.v.Structure = "Bicycle"
return c
}
func (c *BicycleBuilder) GetVehicle() VehicleProduct {
return c.v
}- MotorbikeBuilder — concrete builder for motorbikes:
package builder
type MotorbikeBuilder struct {
v VehicleProduct
}
func (c *MotorbikeBuilder) SetWheels() BuildProcess {
c.v.Wheels = 2
return c
}
func (c *MotorbikeBuilder) SetSeats() BuildProcess {
c.v.Seats = 2
return c
}
func (c *MotorbikeBuilder) SetStructure() BuildProcess {
c.v.Structure = "Motorbike"
return c
}
func (c *MotorbikeBuilder) GetVehicle() VehicleProduct {
return c.v
}- ManufacturingDirector — the orchestrator:
package builder
type ManufacturingDirector struct {
builder BuildProcess
}
func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {
f.builder = b
}
func (f *ManufacturingDirector) Construct() {
f.builder.SetSeats().SetStructure().SetWheels()
}- Running the example:
fmt.Println("*** Example Builder ***")
manufacturingVehicle := builder.ManufacturingDirector{}
bicycleBuilder := &builder.BicycleBuilder{}
manufacturingVehicle.SetBuilder(bicycleBuilder)
manufacturingVehicle.Construct()
bicycle := bicycleBuilder.GetVehicle()
fmt.Printf("Vehicle is %s has %d wheels, %d seats.\n",
bicycle.Structure, bicycle.Wheels, bicycle.Seats)
fmt.Print("*** End of Builder ***\n\n\n")With the output:
*** Example Builder ***
Vehicle is Bicycle has 2 wheels, 1 seats.
*** End of Builder ***IV. Explain the example above
This example demonstrates how the Builder pattern orchestrates step-by-step object construction through a director, decoupling the construction logic from the caller.
1. Setting up the Director and Builder
manufacturingVehicle := builder.ManufacturingDirector{}
bicycleBuilder := &builder.BicycleBuilder{}
manufacturingVehicle.SetBuilder(bicycleBuilder)- A
ManufacturingDirectoris created — it doesn't know how to build any specific vehicle.
- A
BicycleBuilderis injected viaSetBuilder(). The director now delegates all construction steps to this builder.
- The director is decoupled from the product details; it only knows the
BuildProcessinterface.
2. Constructing the vehicle
manufacturingVehicle.Construct()Internally, Construct() calls:
f.builder.SetSeats().SetStructure().SetWheels()Each method mutates the internal VehicleProduct and returns the builder itself (method chaining), so the steps execute fluently in sequence:
3. Retrieving the product
bicycle := bicycleBuilder.GetVehicle()
fmt.Printf("Vehicle is %s has %d wheels, %d seats.\n",
bicycle.Structure, bicycle.Wheels, bicycle.Seats)GetVehicle() returns the fully assembled VehicleProduct. The caller retrieves it directly from the builder — not from the director.
Log result:
Vehicle is Bicycle has 2 wheels, 1 seats.4. Swapping the builder — zero changes to the director
To build a motorbike instead, only the builder needs to change:
motorbikeBuilder := &builder.MotorbikeBuilder{}
manufacturingVehicle.SetBuilder(motorbikeBuilder)
manufacturingVehicle.Construct()
motorbike := motorbikeBuilder.GetVehicle()
// Vehicle is Motorbike has 2 wheels, 2 seats.The director calls the exact same Construct() method. The difference is entirely encapsulated inside MotorbikeBuilder — Seats = 2 and Structure = "Motorbike".
Summary
- The Director controls the sequence of steps — it doesn't care what is being built.
- The Builder controls the details of each step — it doesn't care about the order.
- Swapping builders produces a completely different product without touching the director.
- Method chaining makes the construction fluent and readable.
V. Conclusion
The Builder pattern shines when you need to construct objects that require multiple steps, especially when the same process should produce different results. It cleanly separates what to build (the builders) from how to orchestrate the build (the director).
That said, we do not always need the Builder pattern to solve similar problems. If an object only has a few fields, a simple struct literal or constructor function is perfectly sufficient. The most important thing is to choose the solution that best fits your specific context.
Thank you for taking the time to read this article! 😊
VI. References
- Go Design Patterns (Mario Castro Contreras)
- Full source code for Go design patterns: available here.

