{} The Go Reference

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 ⏱ 4 min read 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

PatternIn the standard libraryHow it shows up
Decoratorgzip.NewWriter, bufio.NewReader, io.MultiWriterEach wraps an io.Writer/io.Reader and adds behavior while keeping the same interface.
Adapterstrings.NewReader, bytes.NewBuffer, http.HandlerFuncMake one type satisfy the interface another API expects.
Strategysort.Slice(s, less), strings.Map, template.FuncMapPass the algorithm in as a function.
Iteratorbufio.Scanner, sql.Rows, range-over-func (Go 1.23+)Walk a sequence without exposing its internals.
Singletonsync.Once, http.DefaultClient, package init()Exactly one instance, initialized once.
Factorysql.Open, crypto/tls config → tls.ConnA function returns an interface; the concrete type is chosen for you.
Template Methodhttp middleware, text/template executionA fixed skeleton with caller-supplied steps.
Chain of Responsibilitynet/http middleware chainsEach handler can act, then pass to the next.
Observer / Pub-Subchannels + goroutines, context.Context cancellationBroadcast a change to many listeners.
Commandos/exec.Cmd, flag actionsAn action captured as a configurable object.
Visitorfilepath.WalkDir, go/ast.InspectAn operation applied across a structure’s nodes.
Null Objectio.DiscardA do-nothing implementation that removes nil checks.
Bridgeio.Writeros.File, net.ConnThe 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")
})

Spot five patterns in one program

This short program quietly uses five patterns from the table — all standard library, no naming required. Edit and Run:

stdlib_patterns.go — editable & runnable
package main

import (
"bufio"
"bytes"
"fmt"
"io"
"sort"
"strings"
)

func main() {
// Decorator + Null Object: MultiWriter fans one write to many sinks;
// io.Discard is a do-nothing writer (no nil checks needed).
var buf bytes.Buffer
w := io.MultiWriter(&buf, io.Discard)
fmt.Fprint(w, "banana apple cherry date")

// Adapter: strings.NewReader makes a string satisfy io.Reader.
// Iterator: bufio.Scanner walks tokens without exposing the buffer.
sc := bufio.NewScanner(strings.NewReader(buf.String()))
sc.Split(bufio.ScanWords)
var words []string
for sc.Scan() {
	words = append(words, sc.Text())
}

// Strategy: the comparison algorithm is passed in as a function.
sort.Slice(words, func(i, j int) bool { return words[i] < words[j] })

fmt.Println("sorted words:", words)
}

Five patterns — Decorator, Null Object, Adapter, Iterator, Strategy — and not one of them needed a class, an implements, or even a comment to exist.

🐹 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.

Check your understanding

Score: 0 / 5

1. gzip.NewWriter(w) wraps an io.Writer and returns an io.Writer that compresses. Which pattern?

It keeps the io.Writer contract and adds compression — a Decorator. bufio.NewWriter and io.MultiWriter are the same idea; chain them to stack behaviors.

2. sort.Slice(s, less) takes the comparison as a function. Which pattern is that?

The sort is fixed; the comparison strategy is supplied as a func. strings.Map and template.FuncMap inject strategies the same way.

3. io.Discard is a writer that throws everything away. Which pattern does it embody?

Instead of checking `if w != nil` everywhere, hand code a Null Object (io.Discard) that satisfies the interface and does nothing — simpler, branch-free call sites.

4. http.HandlerFunc turns a plain func(w, r) into an http.Handler. Which pattern?

HandlerFunc gives a function a ServeHTTP method so it fits the http.Handler interface — a function adapter. sort.Reverse adapts a sort.Interface similarly.

5. Why do these patterns 'feel invisible' in Go?

Where Java needs classes and explicit wiring to express a Strategy or Adapter, Go expresses them as a func value or a type that just has the right methods — so the pattern dissolves into idiomatic code.

Comments

Sign in with GitHub to join the discussion.