Stdlib · Guide · Reference

Patterns in the Go Standard Library

A tour of the classic design patterns hiding in plain sight across Go's standard library.

Stdlib Reference Complete

The best way to internalize a pattern is to spot it in code you already use. Go’s standard library is full of them — often in a form so natural you never noticed there was a “pattern” at all. Here’s a map.

🎯 Why this matters

If you can see that gzip.NewWriter is a Decorator and sort.Slice is a Strategy, you’ve understood both the pattern and idiomatic Go at the same time. Patterns stop being abstract and become muscle memory.

The map

Pattern In the standard library How it shows up
Decorator gzip.NewWriter, bufio.NewReader, io.MultiWriter Each wraps an io.Writer/io.Reader and adds behavior while keeping the same interface.
Adapter strings.NewReader, bytes.NewBuffer, http.HandlerFunc Make one type satisfy the interface another API expects.
Strategy sort.Slice(s, less), strings.Map, template.FuncMap Pass the algorithm in as a function.
Iterator bufio.Scanner, sql.Rows, range-over-func (Go 1.23+) Walk a sequence without exposing its internals.
Singleton sync.Once, http.DefaultClient, package init() Exactly one instance, initialized once.
Factory sql.Open, crypto/tls config → tls.Conn A function returns an interface; the concrete type is chosen for you.
Template Method http middleware, text/template execution A fixed skeleton with caller-supplied steps.
Chain of Responsibility net/http middleware chains Each handler can act, then pass to the next.
Observer / Pub-Sub channels + goroutines, context.Context cancellation Broadcast a change to many listeners.
Command os/exec.Cmd, flag actions An action captured as a configurable object.
Visitor filepath.WalkDir, go/ast.Inspect An operation applied across a structure’s nodes.
Null Object io.Discard A do-nothing implementation that removes nil checks.
Bridge io.Writeros.File, net.Conn The interface separates the consumer from concrete implementations.

A few worth seeing up close

Decorator — io wrappers

// Each layer wraps the previous and keeps the io.Writer interface.
f, _ := os.Create("out.gz")
gz := gzip.NewWriter(f)        // decorate: add compression
buf := bufio.NewWriter(gz)     // decorate: add buffering
fmt.Fprintln(buf, "hello")     // writes flow buf → gz → file

Strategy — sort.Slice

people := []Person{{"Ada", 36}, {"Linus", 54}}
sort.Slice(people, func(i, j int) bool { // the comparison strategy
	return people[i].Age < people[j].Age
})

Adapter — http.HandlerFunc

// A plain function becomes an http.Handler by adapting it.
var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "hi")
})

🐹 The takeaway

Go rarely names these patterns, because its interfaces and functions make them feel like ordinary code. That’s the point: in Go, a “pattern” is usually just the natural shape the language nudges you toward.

As you work through the individual pattern pages, come back here — each one links to its standard-library home so you can connect the theory to code you ship every day.