An interface is a set of method signatures. It says “anything with these methods can be used here.” It describes behavior, not data.
type Speaker interface {
Speak() string
}
Anything that has a Speak() string method satisfies Speaker. Simple as that.
Implicit satisfaction (interview favorite)
Here’s the thing that surprises people coming from Java or C#. In Go there is no implements keyword. A type satisfies an interface automatically just by having the right methods. We never declare the relationship.
type Dog struct{}
func (d Dog) Speak() string { return "Woof" } // that's it
var s Speaker = Dog{} // Dog satisfies Speaker — no "implements" anywhere
fmt.Println(s.Speak()) // Woof
In simple language: if it walks like a duck and quacks like a duck, Go treats it as a duck. We didn’t have to tell Go that Dog is a Speaker.
Small interfaces are the Go way
Go favors tiny interfaces — often just one method. The standard library is full of them. io.Reader and io.Writer each have a single method, and huge chunks of Go plug together through them.
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
The smaller the interface, the more types satisfy it, and the more reusable our code is. “The bigger the interface, the weaker the abstraction” is a famous Go proverb.
The empty interface: interface{} / any
An interface with zero methods is satisfied by everything, since every type has at least zero methods. That’s the empty interface, written interface{} or, since Go 1.18, the alias any.
var x any // can hold literally anything
x = 42
x = "hello"
x = []int{1, 2}
We use it when we genuinely don’t know the type ahead of time (like fmt.Println, which takes ...any). But it throws away type safety, so we use it sparingly.
What an interface actually holds: (type, value)
In simple language, an interface value is a little two-part box. It stores both the concrete type and the value of whatever we put in it.
This two-part design is exactly what causes the next gotcha.
The famous nil-interface gotcha
An interface is nil only when both its type half and its value half are nil. If we stuff a nil pointer into an interface, the interface holds a type (the pointer’s type) — so it is NOT nil, even though the value inside is nil.
type MyErr struct{}
func (e *MyErr) Error() string { return "boom" }
func doThing() error {
var p *MyErr = nil // a nil pointer
return p // BUG: returns a non-nil interface wrapping a nil *MyErr
}
err := doThing()
fmt.Println(err == nil) // false! — surprises everyone
The interface isn’t nil because its type half is *MyErr. This is why we always return a literal nil for the no-error case, never a typed nil pointer.
Interface satisfaction visual
Interview soundbite
Go interfaces are satisfied implicitly — no implements keyword, a type just needs the right methods, which is duck typing. We favor small interfaces like io.Reader, and any is the empty interface that holds anything. Under the hood an interface is a (type, value) pair, and it’s nil only when both halves are nil — which is why returning a nil typed pointer gives a non-nil interface, the classic nil-interface trap.