Interfaces
What Is an Interface?
An interface in Go defines a set of method signatures. Any type that implements all the methods of an interface automatically satisfies it โ there is no implements keyword. This is called implicit satisfaction, and it keeps Go code decoupled and flexible.
Any type with an Area() float64 method satisfies Shape, regardless of where the type is defined or whether it ever mentions Shape by name.
Defining and Using an Interface
Here are two concrete types and a function that works with both through the Shape interface:
Neither Circle nor Rectangle declares that it implements Shape. The compiler checks at the call site โ if the method exists with the right signature, the type satisfies the interface.
The Empty Interface: any
The empty interface has no methods, so every type satisfies it. In Go 1.18+, any is an alias for interface{}:
any appears frequently in generic utility functions, containers, and APIs that must accept arbitrary values. The trade-off is that you lose compile-time type safety โ to use the underlying value in a typed way, you need a type assertion.
Type Assertions
A type assertion extracts the concrete value from an interface variable:
Always prefer the two-value form v, ok := x.(T) over the single-value form v := x.(T), which panics if the assertion fails.
When you need to handle multiple types, a type switch is cleaner than chained assertions:
Interfaces are the primary mechanism for polymorphism in Go โ small, focused interface definitions lead to composable, testable code.
fmt.Stringer
The most widely-used single-method interface in Go is fmt.Stringer, defined in the fmt package:
When fmt.Println, fmt.Printf("%v", ...), or fmt.Printf("%s", ...) receive a value, the fmt package checks at runtime whether the value's type implements Stringer. If it does, String() is called automatically to produce the output.
Implementing Stringer
Why this is Go's interface system in action
The fmt package was written before your Point type existed. Yet it can call your type's String() method โ because Go checks for the method at the call site with no registration, no coupling, and no inheritance. Your type never imports or references fmt to satisfy Stringer; fmt simply checks whether the right method exists.
Other interfaces that work the same way
The same pattern appears throughout the standard library:
errorโError() string: checked byfmtand every function that returns an error. Implement it on any type to make it usable as an error value.io.ReaderโRead([]byte) (int, error): checked by anything that reads bytes โjson.Decoder,http.Request.Body,io.Copy, and more. A type satisfyingio.Readerworks with all of them.
These are all just interfaces. Go checks for them implicitly at compile time; nothing needs to be declared or registered.
Interface Composition
Interfaces can embed other interfaces to form larger contracts. The standard library uses this heavily:
io.Reader and io.Writer are the most important interfaces in Go's standard library. They appear everywhere: files, network connections, HTTP bodies, in-memory buffers. A function that accepts io.Reader works with all of them without knowing which one it has received.
The Interface Nil Pitfall
A common source of bugs: an interface variable is only nil when both its type and value are nil. A typed nil pointer stored in an interface is not nil:
The fix is to return a bare nil (untyped) directly rather than assigning a typed nil to an interface variable. This is why error-returning functions should return nil as the error, not (*MyError)(nil).
Knowledge Check
How does a type satisfy an interface in Go?
What is the safe way to perform a type assertion?
Which method must a type implement to customize how fmt.Println prints it?