Go Programming Series: This lesson is part of our comprehensive Complete Go Tutorial: Learn Go (Golang) from Beginner to Advanced.
Go Interfaces and Polymorphism: Contracts, Method Sets and Practical Design
This is a detailed practical guide to interfaces, implicit satisfaction, small contracts, polymorphism, dependency injection, assertions and type switches. It follows the approved Go article standard: one primary intent, deep explanation, practical examples, design trade-offs, edge cases, testing, performance and production guidance.
What You Will Learn
- Core language rules and terminology
- Idiomatic Go examples
- Design decisions and trade-offs
- Common mistakes and edge cases
- Testing, performance and production practices
At a Glance
| Area | Focus |
|---|---|
| Language | Rules and mental model |
| Practice | Runnable examples |
| Design | APIs and maintainability |
| Reliability | Testing and edge cases |
| Progression | Connection to later Go topics |
1. What Is an Interface?
An interface describes behavior through a set of methods. Code that depends on an interface depends on what a component can do rather than on its concrete implementation.
2. Implicit Satisfaction
Go has no implements keyword. A type satisfies an interface automatically when its method set contains the required methods. This keeps implementations independent from consumer-defined contracts.
Practical Example
type Reader interface {
Read() ([]byte, error)
}
The example is intentionally small. In a production codebase, the same rule should be applied inside a package with clear ownership, validation and tests.
3. Why Small Interfaces Matter
Small interfaces are easier to understand, implement, test and compose. Consumer-focused interfaces also reduce coupling because callers request only the behavior they actually use.
4. Interface Values
An interface value contains dynamic type information and a dynamic value. This explains why different concrete types can be passed through the same interface and why typed nil values can create surprising behavior.
5. Nil Interface vs Typed Nil
A nil interface contains neither a dynamic type nor a value. An interface containing a typed nil pointer is not itself nil. This distinction matters in error handling, optional dependencies and API design.
6. Polymorphism
Polymorphism allows one function to operate on multiple implementations through a common contract. Standard library interfaces such as io.Reader and io.Writer demonstrate this pattern throughout Go.
7. Dependency Injection
A service can accept a small interface instead of constructing a database, notifier or external client directly. Tests can then supply a deterministic implementation while production uses the real component.
Practical Example
value, ok := item.(string)
if ok { fmt.Println(value) }
The example is intentionally small. In a production codebase, the same rule should be applied inside a package with clear ownership, validation and tests.
8. Interfaces and Testing
Interfaces are useful at external boundaries, but creating interfaces solely to generate mocks can lead to unnecessary abstraction. Introduce them where substitution or decoupling is meaningful.
9. Type Assertions
A type assertion extracts a concrete value from an interface. The comma-ok form allows code to handle a failed assertion safely instead of panicking.
10. Type Switches
A type switch selects behavior based on the dynamic type stored in an interface. It is appropriate at controlled boundaries, but widespread type switching can indicate that the abstraction is not well designed.
Practical Example
type Reader interface {
Read() ([]byte, error)
}
The example is intentionally small. In a production codebase, the same rule should be applied inside a package with clear ownership, validation and tests.
11. any and Empty Interfaces
interface{} and its modern alias any accept values of any type. They are useful for genuinely dynamic data, but excessive use removes compile-time guarantees and makes APIs harder to reason about.
12. Interface Composition
Interfaces can embed other interfaces. This allows larger contracts to be assembled from smaller behaviors while keeping the individual contracts focused.
13. Standard Library Interfaces
Interfaces such as error, io.Reader, io.Writer and fmt.Stringer show how Go uses behavior-oriented contracts instead of class hierarchies.
14. Interfaces at Package Boundaries
Interfaces are especially valuable where a package consumes behavior supplied by another component. Defining the smallest required contract close to the consumer can reduce dependencies.
15. Performance Considerations
Interface dispatch has a cost, but it is rarely the first optimization concern. Clear architecture and measured profiling are more important than avoiding interfaces based on speculation.
16. Common Mistakes
Frequent mistakes include large interfaces, premature abstraction, returning interfaces when concrete types are clearer, overusing type assertions and misunderstanding typed nil values.
17. Best Practices
Keep interfaces small, define them around actual consumer needs, rely on implicit satisfaction, use concrete types until abstraction provides value, and keep dynamic type handling at clear boundaries.
18. Frequently Asked Questions
Interfaces are not only for mocking. They are a core Go abstraction mechanism. A type does not need to know about an interface to satisfy it, and small interfaces generally create more reusable boundaries.
19. Conclusion
Interfaces provide Go's most important form of behavioral abstraction. When used with small contracts, composition and dependency injection, they create flexible systems without class inheritance.
Practical Decision Guide
| Technique | Best when | Caution |
|---|---|---|
| Small interface | Consumer needs behavior | Premature abstraction |
| Concrete type | Implementation is stable | Boundary coupling |
| Assertion | Controlled dynamic behavior | Scattered type checks |
| Injection | Dependency must be replaceable | Over-mocking |
Engineering Workflow
Start with the smallest working example, run it, then introduce invalid and boundary cases. Move the code into a package and write table-driven tests around public behavior. Only after correctness is established should performance be investigated with profiling or compiler diagnostics. This workflow keeps Go code simple while still producing professional engineering results.
Common Review Questions
How does implicit interface satisfaction work?
A strong answer should state the language rule, demonstrate it with a small example, and then explain the practical trade-off. In code review, clarity of ownership, mutation and API behavior is more valuable than memorizing isolated definitions.
Why are small interfaces preferred?
A strong answer should state the language rule, demonstrate it with a small example, and then explain the practical trade-off. In code review, clarity of ownership, mutation and API behavior is more valuable than memorizing isolated definitions.
What is a typed nil?
A strong answer should state the language rule, demonstrate it with a small example, and then explain the practical trade-off. In code review, clarity of ownership, mutation and API behavior is more valuable than memorizing isolated definitions.
When should type assertions be used?
A strong answer should state the language rule, demonstrate it with a small example, and then explain the practical trade-off. In code review, clarity of ownership, mutation and API behavior is more valuable than memorizing isolated definitions.
Best-Practice Checklist
- Prefer idiomatic Go over patterns copied from other languages.
- Make mutation, ownership and nil behavior intentional.
- Keep APIs small and understandable.
- Use the zero value where it is genuinely useful.
- Test observable behavior and important edge cases.
- Measure performance before optimizing.
- Document exported behavior that callers need to understand.
Conclusion
Interfaces provide Go's most important form of behavioral abstraction. When used with small contracts, composition and dependency injection, they create flexible systems without class inheritance.