Interfaces

intermediate go interfaces

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.

interface value (Speaker)
type
Dog
value
Dog{}
an interface is nil only when BOTH halves are nil

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

Dog
has Speak() string
→ automatically satisfies →
Speaker
needs Speak() string

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.