I. Template Method — Behavioral Pattern
Template Method fixes the overall shape of an algorithm in one place, while letting individual steps vary. The sequence of steps — and which ones are fixed versus which ones change — is defined once; only the varying steps are supplied by the caller.
This solves the problem of duplicating an algorithm's skeleton every time one step needs to change. Without Template Method, adding a new variant usually means copying the whole sequence and editing the one step that's actually different, so the fixed parts drift out of sync as each copy gets modified independently over time. Template Method keeps the fixed sequence in exactly one place and isolates the varying parts behind a small contract.
Classic Template Method (in languages with inheritance) puts the fixed algorithm in a base class method and lets subclasses override specific steps. Go has no inheritance, so this example expresses the same idea with a free function that takes an interface: the function Prepare is the template method, and Beverage is the contract for the steps that vary.
II. Real-world Example
A coffee shop where every hot drink is prepared with the same four-step sequence, but the middle steps differ by drink:
- Beverage (interface) — declares the three steps that vary between drinks:
Brew(),PourInCup(),AddCondiments().
- Prepare (the template method) — a free function that takes any
Beverageand returns the four steps in a fixed order: boil water (identical for every drink), thenBrew(),PourInCup(), andAddCondiments()— all delegated to whicheverBeveragewas passed in.
- Coffee — implements the three steps for coffee: drip through a filter, pour into a cup, add sugar and milk.
- Tea — implements the same three steps for tea: steep the leaves, pour into a cup, add lemon.
Prepare never changes regardless of which beverage it's given — only the three delegated steps change.

III. Implementation
beverage.go
package templatemethod
type Beverage interface {
Brew() string
PourInCup() string
AddCondiments() string
}
func Prepare(beverage Beverage) []string {
return []string{
"Boiled water",
beverage.Brew(),
beverage.PourInCup(),
beverage.AddCondiments(),
}
}coffee.go
package templatemethod
type Coffee struct{}
func (c Coffee) Brew() string {
return "Dripped coffee through filter"
}
func (c Coffee) PourInCup() string {
return "Poured coffee into cup"
}
func (c Coffee) AddCondiments() string {
return "Added sugar and milk"
}tea.go
package templatemethod
type Tea struct{}
func (t Tea) Brew() string {
return "Steeped tea leaves"
}
func (t Tea) PourInCup() string {
return "Poured tea into cup"
}
func (t Tea) AddCondiments() string {
return "Added lemon"
}main.go (usage)
for _, step := range templatemethod.Prepare(templatemethod.Coffee{}) {
fmt.Println(step)
}
for _, step := range templatemethod.Prepare(templatemethod.Tea{}) {
fmt.Println(step)
}IV. Explain the example above
templatemethod.Prepare(templatemethod.Coffee{})runs the fixed sequence: it starts with"Boiled water", then callsCoffee{}.Brew(),Coffee{}.PourInCup(), andCoffee{}.AddCondiments(), collecting all four strings into a slice.
- The loop prints each step of the coffee sequence in order.
templatemethod.Prepare(templatemethod.Tea{})runs the exact same four-step sequence — the same function, unchanged — but this time steps 2 through 4 delegate toTea{}instead.
- The loop prints each step of the tea sequence in order.
Running it produces:
*** Example Template Method ***
Boiled water
Dripped coffee through filter
Poured coffee into cup
Added sugar and milk
Boiled water
Steeped tea leaves
Poured tea into cup
Added lemonNotice "Boiled water" appears identically in both runs — it's hard-coded inside Prepare and never delegated. Everything else changes based on which Beverage is passed in, without Prepare itself containing a single conditional branch on drink type.
V. Conclusion
Template Method earns its keep when several variants share most of a process and differ only in a few well-defined steps. Fixing the sequence in one place means the shared parts can never drift out of sync between variants, and adding a new variant is just implementing the varying steps — the sequence itself never needs to change or be reviewed again.
The trade-off is rigidity: if a future variant needs to reorder steps, skip one, or insert a new one in the middle, the fixed sequence has to change for everyone, since it isn't part of what varies. For two variants that only share one or two steps in common, keeping them as fully separate functions is often clearer than forcing them through a shared skeleton that barely fits either one. Template Method pays off once the shared sequence is stable and only the delegated steps are expected to grow.
No pattern is universally superior.
VI. References
- Go Design Patterns (Mario Castro Contreras)




