How does interface satisfaction work in Go, compared to Java or C#?

4 minbeginnergointerfacesstructural-typingfundamentals

Quick Answer

Go interfaces use structural typing (also called implicit or duck typing): a type satisfies an interface simply by implementing all its methods, with no implements keyword or declared relationship needed anywhere. This is the opposite of Java or C#, where a class must explicitly declare implements SomeInterface at the point it's defined. In Go, you can define an interface after a type already exists, and if that type happens to have the right methods, it already satisfies the interface with zero code changes to the type itself. This makes interfaces cheap to introduce for testing or decoupling, and lets unrelated packages define compatible interfaces without either one depending on the other.

Detailed Answer

Structural typing is one of the most distinctive differences between Go and mainstream OOP languages.

No implements keyword needed

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

type ConsoleLogger struct{}

func (c ConsoleLogger) Write(p []byte) (int, error) {
    fmt.Print(string(p))
    return len(p), nil
}

var w Writer = ConsoleLogger{}  // satisfies Writer automatically — no declaration needed

ConsoleLogger never mentions Writer anywhere. It satisfies the interface purely because it has a matching Write method.

Defining an interface after the fact

// Some third-party package already defines this, with no idea your interface exists:
type ThirdPartyThing struct{}
func (t ThirdPartyThing) Write(p []byte) (int, error) { return len(p), nil }

// You can define your own interface and use it immediately, with no changes to ThirdPartyThing:
type MyWriter interface {
    Write(p []byte) (n int, err error)
}
var w MyWriter = ThirdPartyThing{}  // works, no modification needed

Java/C# comparison

// Java requires explicit declaration
class ConsoleLogger implements Writer {
    public int write(byte[] p) { ... }
}

In Java or C#, ConsoleLogger must declare implements Writer at its definition site. If you want a new interface applied to an existing class you don't control, you're stuck wrapping it, since you can't retroactively add implements to someone else's class.

Why this matters practically

Structural typing lets you define small, narrow interfaces exactly where you need them, like in a test file, without touching the type being tested at all. This is a big part of why Go encourages small, single-method interfaces (io.Reader, io.Writer) defined by the consumer of an interface, not the type that happens to implement it.

Related Resources