tezvyn:

Go Interfaces: Describe Behavior, Not Data

AI-drafted, machine-checkedSource: go.devbeginner

Go interfaces define behavior, not data. A type satisfies an interface implicitly by implementing its methods, without an `implements` keyword. This enables writing flexible functions, like `io.Writer` handling files or HTTP responses.

WHY IT EXISTS To allow functions to operate on values of different concrete types without knowing their specific implementation, promoting flexible and decoupled code. Instead of inheriting from a base class, Go types can satisfy multiple independent behavioral contracts, enabling composition over inheritance.

THE MENTAL MODEL Think of an interface as a job description listing required skills (methods). A struct is a candidate for that job. If the candidate has all the required skills, it implicitly gets the job (satisfies the interface). There's no need to formally declare "I am applying for this job." This is called structural typing: if it walks and talks like a duck, Go considers it a duck.

HOW IT WORKS An interface type specifies a method set. Any type that defines all methods in that set is said to implement the interface. For example, if an IOWriter interface has a Write(p []byte) (n int, err error) method, any type with that exact method signature (like os.File or bytes.Buffer) can be used wherever an IOWriter is expected. The Go compiler checks this satisfaction automatically at compile time.

WHEN TO USE IT Use interfaces as function parameters to accept multiple types of data sources or sinks (e.g., io.Reader). Use them to define service contracts for dependency injection and to create mocks for testing. A common Go proverb is "accept interfaces, return structs," which encourages functions to depend on abstract behaviors while returning concrete, ready-to-use types.

WHEN NOT TO USE IT Avoid creating interfaces prematurely. Start with concrete types and introduce an interface only when a second type needs to be handled polymorphically. The empty interface, interface{} (an alias for any), accepts any value, but it bypasses static type checking and should be used sparingly. Using it requires type assertions or switches to access the underlying value, which can be verbose and error-prone.

ONE CANONICAL EXAMPLE The http.Handler interface defines how to serve HTTP requests. It has just one method: ServeHTTP(ResponseWriter, *Request). Any struct that implements this method can be passed to http.Handle, which registers it to handle requests for a given pattern. This decouples the core routing logic from the specific logic of each endpoint.

Read the original → go.dev

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.