๐Ÿ”ŒInterfacesLESSON

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 by fmt and 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 satisfying io.Reader works 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?