The builder pattern is a creational design pattern. It is used when the creation of a product involves multiple, independent steps.

What's the one thing that immediately comes to your mind when you think of something that requires multiple steps to be "built"? That's right, subway!! 🥙🤤 You choose your bread, you choose if you want cheese, you choose your toppings and finally your sauces!

I'm sure you understand the process of creating a sub. Let's see what it would look like if we coded it using the Builder pattern:


Let us assume that we have 2 different types(recipes) for a sub - Veggie Delight and Chicken Teriyaki. Each of these would have a "Builder" dedicated to them.

The interface that these builders implement will be the same - That way, we can easily add more types(recipes) to our menu.

type sub struct {
    bread string
    hasCheese bool
    toppings []string
    sauces []string
type iSubBuilder interface {
    getSub() sub
type veggieDelightBuilder struct {

func (v *veggieDelightBuilder) setBread() {
    v.sub.bread = "parmesan oregano"

func (v *veggieDelightBuilder) setCheese() {
    v.sub.hasCheese = false

func (v *veggieDelightBuilder) setToppings() {
    v.sub.toppings = []string{"olives", "tomatoes", "onions", "jalapeños"}

func (v *veggieDelightBuilder) setSauces() {
    v.sub.sauces = []string{"south west"}

func (v *veggieDelightBuilder) getSub() sub {
    return v.sub
type chickenTeriyakiBuilder struct {

func (c *chickenTeriyakiBuilder) setBread() {
    c.sub.bread = "italian"

func (c *chickenTeriyakiBuilder) setCheese() {
    c.sub.hasCheese = true

func (c *chickenTeriyakiBuilder) setToppings() {
    c.sub.toppings = []string{"roasted chicken", "olives", "onions", "jalapeños"}

func (c *chickenTeriyakiBuilder) setSauces() {
    c.sub.sauces = []string{"chilli", "bbq"}

func (c *chickenTeriyakiBuilder) getSub() sub {
    return c.sub

We've successfully created the sub and the builders. As you may have observed, we can easily create another recipe and have it implement all the functions.

Now, let's create an optional director struct that would accept accept a builder and then build subs for us.

The director is not always part of the builder pattern. You could look at it as an added layer of abstraction for cleaner code

type director struct {
    builder iSubBuilder

func (d *director) setBuilder(builder iSubBuilder) {
    d.builder = builder

func (d *director) buildSub() sub {

    return d.builder.getSub()

Aaand we're done 🎉

Let's see it in action:

func describeSub(sub sub) {
    fmt.Printf("bread: %s, cheese: %t, toppings: %s, sauces: %s\n", sub.bread, sub.hasCheese, strings.Join(sub.toppings, ", "), strings.Join(sub.sauces, ", "))

func main() {
    veggieDelight := &veggieDelightBuilder{}
    director := &director {
        builder: veggieDelight,
    veggieDelightSub := director.buildSub()


    chickenTeriyakiSub := director.buildSub()


Running this program should get you this in your terminal

bread: parmesan oregano, cheese: false, toppings: olives, tomatoes, onions, jalapeños, sauces: south west
bread: italian, cheese: true, toppings: roasted chicken, olives, onions, jalapeños, sauces: chilli, bbq

You can find all the code for this tutorial on this github repo

Hope this made understanding the Builder pattern easier 🚀

Cheers ☕️