go interfaces are weird but good

2025-07-25

go interfaces are weird. you don't implement them. you just write methods and if they match an interface signature, you implement it. automatically.

this feels wrong when you first see it. like the compiler is making assumptions about your code that you didn't explicitly state.

but it's actually one of go's best design decisions.

why explicit is limiting

most languages make you declare your intent upfront. you have to say "this type implements this interface" before you can use it as that interface.

but what if you write a type, and later someone else defines an interface that your type could satisfy? in most languages, you're stuck. your type doesn't implement their interface, even though it has all the right methods.

this creates artificial coupling. types have to know about interfaces that might not even exist when you write the type.

structural typing is better

go's approach is different. interfaces are satisfied implicitly. if your type has the right methods, it implements the interface. period.

type Writer interface {
    Write([]byte) (int, error)
}

type MyThing struct{}

func (m MyThing) Write(data []byte) (int, error) {
    // just by having this method, MyThing implements Writer
    return len(data), nil
}

no explicit declaration. no coupling. your type implements any interface that matches its methods.

this means libraries can define interfaces that work with types that were written years before the library existed.

real world example

look at io.Writer. it's just one method:

type Writer interface {
    Write([]byte) (int, error)
}

how many types implement this? files, network connections, buffers, compression streams, http responses, loggers. none of them explicitly declare that they implement Writer. they just have a Write method.

this is why go's standard library is so composable. everything just fits together naturally.

the downside

there is one real problem: you can accidentally implement an interface.

say you write a type with a Close() method. congratulations, you now implement io.Closer. your type can be passed to anything expecting a Closer.

if your Close method doesn't actually close anything important, this could be a problem.

but in practice, this rarely happens. method names tend to be meaningful, and if you're writing a Close method, you probably mean for it to close something.

why it works

go's implicit interfaces work because they force you to think differently about design.

instead of designing big interfaces upfront, you write small, focused interfaces that describe specific behaviors. most go interfaces have 1-3 methods max.

instead of thinking "what interfaces should this type implement?", you think "what does this type actually do?"

the interfaces emerge naturally from the behavior you actually need.

the lesson

sometimes what feels weird at first is actually better design.

go's interfaces felt wrong when i first encountered them. they seemed undisciplined and error prone.

but they're not. they're just a different way of thinking about abstraction. one that leads to more composable, less coupled code.

once you get used to them, explicit interface declarations feel unnecessarily rigid.