The Builder pattern, one of the original Gang of Four design patterns, provides a strategy for creating complex objects by splitting their construction process from their actual creation. It makes it even possible to reuse this construction process to build objects of different types.
Or, in simpler words, the pattern makes the whole creation of objects maximally configurable:
It not only defines how an object is built but even what objects to built.
And as that is not enough, using the pattern you can even share the same construction process for objects that aren't in the same inheritance hierarchy!
However, despite its historical significance, it's only partially reflected in modern software. While you may still find many of its aspects today, the pattern in its complete form is not used that often anymore, as there are usually easier strategies available to solve some of the challenges it addresses.
Nevertheless, while there may not be many scenarios for this pattern, its use can sometimes still be beneficial, so it's definitely worth a closer look.
Generally, the pattern consists of two subsequent steps. Let's start with the easier one first.
The Builder Pattern, Part I:
Separating construction from creation
That sounds maybe more complicated than it actually is. We will look into this concept based on an example.
Let's say we are developing a video game - a real-time strategy game, for instance. An integral part of our game are the different levels where players have to place their units and face the enemy's army.
For this, we define a Level class that describes the structure and attributes of such levels. However, since our class can represent many different types of levels, various configuration options must be passed via the constructor.
This approach has a few drawbacks:
First, as we see, our constructor is relatively complex. Callers always need to provide all parameters, even if some may not always be relevant - for instance, a mountain range level won't need any rivers. Creating different levels would require us to make constructor calls with long parameter chains, which can be hard to read. Look at the following code - It is not immediately obvious what the empty arrays in the parameter list represent.
// create plains
plainsLevel = new Level(
new Size(20,20), Terrain.GRASS, [], [new River()], []
);
// create mountain range
mountainRangeLevel = new Level(
new Size(20,20), Terrain.ROCKS, [], [], [new Mountain(), new Mountain()]
);
Second, our implementation requires us to provide all parameters immediately when creating the level object. There is no way of configuring the level now and deferring its creation to later. This can be a disadvantage if, for instance, the generation of the level takes very long.
We can solve those two issues quite easily by introducing a new class LevelBuilder, which works as a type of buffer that takes all the parameters and creates the Level object only on demand when calling the getLevel()
method. Additionally, our LevelBuilder could also provide default values for all parameters. Callers have to invoke setters only if they need to override those default values.
Using the LevelBuilder class to configure and create new levels makes our code more convenient and readable. Compare the implementation below with the constructor calls we had to invoke earlier.
// create plains
levelBuilder.setTerrain(Terrain.GRASS);
levelBuilder.addRiver();
plainLevel = levelBuilder.getLevel();
// create mountain range
levelBuilder.setTerrain(Terrain.ROCKS);
levelBuilder.addMountain();
levelBuilder.addMountain();
mountainRangeLevel = levelBuilder.getLevel();
Even in our small example with only five attributes, the code looks already easier to read and understand. Now think about what would be if the Level class had not five but ten or even more attributes. Using our LevelBuilder, we can focus only on the relevant parameters and let the Builder assign default values to all other attributes.
Great, this was already the first part of the Builder pattern - we've simplified the construction process and separated it from the actual creation of the object. Not bad, but there is still one problem: Both, construction and creation remain coupled in the same class, so we cannot reuse our construction process for other classes yet.
Let's see how we can achieve this.
The Builder Pattern, Part II:
Using the same construction process for different representations
For the second part of the pattern, we need to introduce a new class, the Director. Its purpose is to coordinate or direct the construction process of a Level. Our Director could have one method, createPlains()
, that builds a level representing a terrain like the Great Plains, or a method createMountainRange()
that sets up a mountain range level.
The important part is that while the Director knows how to construct a level, it should not know how to create it, so we have to remove all code used to instantiate the Level object. In our case, that's luckily only the LevelBuilder's getLevel()
method.
If we do this, we have clearly separated the construction from the creation; both the Director and the LevelBuilder know how to construct a Level, but neither of them knows how to create it.
Okay, fine, but how do we actually create any levels now if there is no method for it?
By subclassing our LevelBuilder - For every different representation of a Level we want to create, we define a new LevelBuilder subclass.
In the case of our game, we could, for instance, categorize Level classes based on different rendering engines. One LevelBuilder could create 2D levels while another generates 3D representations of our levels.

Note that while the LevelBuilders are part of a class hierarchy, the actual Levels don't need to be related. A 2D Level and a 3D Level do not necessarily require the same base class, although they're constructed by the same process.
However, as the Director operates only with the LevelBuilder base class, we must add another layer to our pattern, where we instantiate the concrete LevelBuilder subclass and inject it into the Director.
This coordination at the root level is done by the Client class. The Client acts as the entry point for our pattern by instantiating and connecting the concrete LevelBuilder and the Director. It then starts the construction process and retrieves the object finally created.
Using this approach, we can implement a game with two different representations, either in 2D or 3D. While each representation uses different level visualizations, they still share the whole creation process for each level.
And that's it: By adding the Director and the Client, we have our final Builder pattern. It allows us to encapsulate a potentially complex construction process into a separate class and lets us reuse this process for different types of Products we want to create. If we want to add new Products to our system, we only have to implement a new Builder and inject it into our Director. This makes extending our system very easy.
The Builder Pattern Today
The Builder pattern undoubtedly introduced some interesting concepts. However, from today's perspective, it is rarely applied as a whole. But still, many of its concepts found their way into different programming languages and frameworks.
Take, for example, the Lombok Java library: It allows you to simply annotate your code with @Builder to generate an entire Builder API.
Also, most languages nowadays support optional parameters similar to the ones used in the Builder class, so there is no need to use a dedicated class to simplify object creation. The below Dart code uses optional constructor parameters, so programmers only have to set the arguments they wish to override.
class Level {
...
// define a constructor with
// default parameters.
Level({
this.size = Size(20,20),
this.terrain = Terrain.GRASS,
this.rivers = []
});
}
// create Level and set parameters where necessary
Level level = Level(terrain = Terrain.ROCKS);
Regarding the pattern's ability to reuse the same construction process for different classes, I would argue this feature is rarely used today.
The reason may be that unrelated classes using the same creation process are relatively unusual. Take, for instance, the example in this article: How likely would you have a game where you could switch the engine between 2D and 3D rendering while keeping the same level structure? The only game I know where this is even possible is Dragon Quest 11, and even there, most of the 2D levels were crafted independently of their 3D counterparts.
A more typical approach today would be to generate an intermediate format with a single Builder and then transform it into the desired output. This simplifies implementation and eliminates the need for a separate Director.
- Level images created with ChatGPT