TypeScript's own domain already established structural typing — a type matches an interface by shape, not by declaration. Go applies the exact same underlying idea to an entire compiled language, and takes it one real step further than TypeScript does: a type never even mentions which interfaces it satisfies.
3 min read
type Speaker interface {
Speak() string
}A Go interface declares a set of method signatures a type must implement — conceptually similar to an interface in OOP & Design Patterns' own lesson on interfaces and abstract classes, but with one structural difference that changes everything about how it's used in practice.
type Dog struct{ Name string }
func (d Dog) Speak() string { return d.Name + " says Woof" }
type Robot struct{ ID string }
func (r Robot) Speak() string { return r.ID + " says Beep" }
// Neither Dog nor Robot EVER mentions "Speaker" anywhere in their own code
var s Speaker = Dog{Name: "Rex"} // compiles — Dog happens to have Speak()
var s2 Speaker = Robot{ID: "R2"} // compiles — Robot happens to have Speak() tooDog and Robot never declare implements Speaker anywhere — Go's compiler simply checks, at the point a value is used as a Speaker, whether that value's type has a matching Speak() string method. If it does, it satisfies the interface automatically. This is the same structural-typing principle TypeScript's own domain covered ("if it has the right shape, it's compatible") applied to a compiled language with zero runtime type-checking overhead — and Go goes one step further than TypeScript even needs to: a type doesn't need to be designed with any particular interface in mind at all, and can satisfy interfaces written by a completely different team, in a completely different package, after the fact.
A package defining a Speaker interface doesn't need to know, or care, about every type that will eventually satisfy it — a completely unrelated package can define its own type, give it a Speak() method for entirely unrelated reasons, and it automatically becomes usable anywhere a Speaker is expected, with zero coordination between the two packages. This is a genuinely different relationship between a type and the interfaces it satisfies than class-based inheritance creates: the interface describes a capability a caller needs, not an identity a type must explicitly claim.
func WriteLog(w io.Writer, message string) {
w.Write([]byte(message))
}Go's standard library defines tiny, single-method interfaces like io.Writer (anything with a Write([]byte) (int, error) method) precisely because implicit satisfaction means anything — a file, a network connection, an in-memory buffer, a custom logger a user writes — can be passed to WriteLog above without that type ever needing to know io.Writer exists, let alone explicitly declare compatibility with it.
Reading a struct's own definition doesn't tell you which interfaces it satisfies — that information only becomes visible at the specific call sites where it's actually used as one. This is the real cost of implicit satisfaction: a genuine gain in decoupling, paid for with a type's full set of "contracts" no longer being visible in one place, unlike an explicit implements clause.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. How does a Go type indicate which interface it satisfies?
2. Why can Go's io.Writer interface be satisfied by types its author never anticipated?
3. What is the real, honest trade-off implicit interface satisfaction accepts?
Go