Go Interfaces
The Power of "If It Walks Like a Duck"
If you are coming to Go from languages like Java or C#, interfaces are likely one of the most confusing concepts to grasp. In those languages, interfaces are explicit contracts: “I promise to obey these rules,” signed in blood with the implements keyword.
In Go, interfaces are magical because they are implicit. There is no signing ceremony. There is no implements keyword.
If a type has the methods required by an interface, it automatically satisfies that interface. This subtle difference changes everything about how you design software.
1. What is an Interface in Go?
At its core, an interface is just a set of method signatures. It describes behavior, not data. It tells the compiler: “I don’t care what this object is, I only care what it can do.”
Here is a simple definition:
// A 'Speaker' is anything that knows how to Speak
type Speaker interface {
Speak() string
}Any type that possesses a Speak() string method is automatically a Speaker.
2. The Magic of Implicit Implementation
This is where Go shines. Let’s create two completely unrelated structs: a Dog and a Robot.
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "Woof!"
}
type Robot struct {
ModelNumber int
}
func (r Robot) Speak() string {
return "Beep Boop."
}Notice that neither Dog nor Robot mentions the Speaker interface. They don’t know it exists. Yet, because they both have the Speak() method, they are both Speakers.
This is often called Duck Typing: “If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.”
Duck typing in Go refers to implicit interface implementation, where a type implements an interface simply by defining its methods, without explicitly declaring "implements." If an object has the necessary method signatures, Go treats it as that type, fulfilling the principle: "If it walks like a duck and quacks like a duck, it is a duck".
3. Polymorphism in Action
Why is this useful? It allows you to write functions that accept abstract behaviors rather than concrete types.
// This function doesn't care if you pass a Dog, a Robot, or a Martian.
// It just needs something that can Speak.
func MakeItTalk(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
d := Dog{Name: "Buddy"}
r := Robot{ModelNumber: 101}
MakeItTalk(d) // Output: Woof!
MakeItTalk(r) // Output: Beep Boop.
}This decoupling is powerful. You can add a Cat struct a year from now, and MakeItTalk will work with it instantly without needing a single line of code changed.
𝐋𝐞𝐚𝐫𝐧 𝐭𝐨 𝐛𝐮𝐢𝐥𝐝 𝐆𝐢𝐭, 𝐃𝐨𝐜𝐤𝐞𝐫, 𝐑𝐞𝐝𝐢𝐬, 𝐇𝐓𝐓𝐏 𝐬𝐞𝐫𝐯𝐞𝐫𝐬, 𝐚𝐧𝐝 𝐜𝐨𝐦𝐩𝐢𝐥𝐞𝐫𝐬, 𝐟𝐫𝐨𝐦 𝐬𝐜𝐫𝐚𝐭𝐜𝐡. Get 40% OFF CodeCrafters: https://app.codecrafters.io/join?via=the-coding-gopher
4. The “Empty Interface” (interface{} or any)
You will often see interface{} (or the newer alias any) in Go code.
An empty interface specifies zero methods. Since every type in Go has at least zero methods, every type satisfies the empty interface.
This is how Go handles generic data containers (before Generics were introduced) or functions like fmt.Println:
func PrintAnything(v interface{}) {
fmt.Printf("I see a value: %v\n", v)
}
PrintAnything(42) // Works
PrintAnything("hello") // Works
PrintAnything(Dog{}) // WorksWarning: While
anyis flexible, it removes type safety. You force the runtime to handle types rather than the compiler. Use it sparingly.
5. Type Assertions: Getting the Concrete Value Back
Sometimes you have an interface, but you need to know what’s underneath to access a specific field (like the Dog’s Name). You use a Type Assertion.
func Analyze(s Speaker) {
// Try to convert 's' back into a Dog
if dog, ok := s.(Dog); ok {
fmt.Println("It's a dog named:", dog.Name)
} else {
fmt.Println("It's not a dog.")
}
}The ok boolean is crucial. If you don’t check it and the assertion fails, your program will panic and crash.
6. Best Practices: “Accept Interfaces, Return Structs”
This is the golden rule of Go API design.
Accept Interfaces: Your functions should be flexible. If you are writing a function to save a file, don’t ask for
*os.Filespecifically. Ask forio.Writer. This allows users to pass a file, a network connection, or a bytes buffer (for testing).Return Structs: Your functions should generally return concrete types (pointers or structs), not interfaces. Let the consumer of your return value decide which interface they want to use it as.
Summary
Go interfaces are a tool for defining behavior. By decoupling your code from concrete implementations (like Dog) and focusing on capabilities (like Speaker), you make your software easier to test, extend, and maintain.




