Interfaces and Composition
Interfaces and Composition
Go has no implements keyword; interfaces are satisfied implicitly. That makes composition over inheritance natural, and lets gortex swap tracing from in-memory to OTel without touching business code.
Small interfaces, implicit satisfaction
A type satisfies an interface simply by having its methods — no declaration needed:
type Stringer interface { String() string }
type Point struct{ X, Y int }
func (p Point) String() string { return fmt.Sprintf("(%d,%d)", p.X, p.Y) }
// Point is a Stringer automatically; no "implements" anywhere
The convention is to keep interfaces small. The standard library’s io.Reader and io.Writer have one method each, which is why they compose everywhere. Two Go proverbs nail it: “the bigger the interface, the weaker the abstraction”, and “accept interfaces, return structs”. Take a small interface as a parameter and callers can pass the widest range of things.
Composition over inheritance: embedding
Go has no inheritance; you compose with embedding plus method promotion:
type Server struct {
*log.Logger // embedded; Server gets Logger's Printf and friends directly
}
s := &Server{Logger: log.Default()}
s.Printf("up") // promoted from the embedded Logger
type ReadWriter interface { // interfaces embed too
io.Reader
io.Writer
}
This isn’t subclassing — it just promotes the embedded type’s method set to the outer type. To override, define a method of the same name on the outer type; there’s no super.
Dependency inversion and adapters
Program against a small interface rather than a concrete type and the implementation becomes swappable. An adapter wraps a foreign type to satisfy the interface you want.
Gortex’s tracing is the ready example: the framework depends on a single Tracer interface, wired by default to an in-memory SimpleTracer; to adopt OpenTelemetry you drop in an OTel adapter implementing the same interface, and business code doesn’t change a line (see B7, “Observability: Health Checks and Tracing”, gortex-observability-health-tracing).
From Effective Go: Interfaces, Embedding
Effective Go’s Interfaces and methods frames an interface as a set of methods, with one type free to satisfy many. Embedding covers interface and struct embedding, how methods are promoted, and why it isn’t inheritance. Together they are how Go organises code: composition and small interfaces, not a type hierarchy.
Takeaways
- Interfaces are satisfied implicitly: having the methods is enough, no
implementsto declare. - Small interfaces are the most useful — “the bigger the interface, the weaker the abstraction”.
- Embedding is composition plus method promotion, not inheritance; a same-named method on the outer type overrides.
- Program to an interface and add an adapter to keep implementations swappable; gortex’s
Traceris exactly this (see B7). - Interface-driven design runs through the whole framework — you’ll see it throughout the framework posts.
Further reading: the series goes on to dissect the gortex framework built on these ideas — source at yshengliao/gortex.
Outline by Sheng, drafted with Claude · Go 1.24 (latest release at the time) · compiled retroactively · part of the 2026-06-13 blog renovation, paint still drying.