Interfaces
Interfaces
Introduction
In this lesson, you'll learn about interfaces in Go. Coming from C#, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In C#, you're familiar with interfaces.
Go has its own approach to interfaces, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
package main
import "fmt"
type Animal interface {
Speak() string
}
type Dog struct{}
type Cat struct{}
// No explicit "implements" — just implement the methods
func (d Dog) Speak() string { return "Woof!" }
func (c Cat) Speak() string { return "Meow!" }
func main() {
var a Animal = Dog{}
fmt.Println(a.Speak())
}Comparing to C#
Here's how you might have written similar code in C#:
interface IAnimal {
string Speak();
}
class Dog : IAnimal {
public string Speak() => "Woof!";
}
class Cat : IAnimal {
public string Speak() => "Meow!";
}
IAnimal a = new Dog();
Console.WriteLine(a.Speak());You may be used to different syntax or behavior.
Go interfaces are satisfied implicitly — no 'class Dog : IAnimal' declaration needed
You may be used to different syntax or behavior.
If a type has all the methods, it automatically implements the interface
You may be used to different syntax or behavior.
This is called structural typing (duck typing at compile time)
You may be used to different syntax or behavior.
Small interfaces are idiomatic in Go — often just one method
You may be used to different syntax or behavior.
The empty interface (interface{} or any) accepts any value — like C# object
Step-by-Step Breakdown
1. Implicit Implementation
In C# you explicitly declare which interface a class implements. In Go you just implement the methods — the compiler figures out the rest.
interface IShape {
double Area();
}
class Circle : IShape { // explicit
public double Area() => Math.PI * r * r;
}type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
// No ": Shape" anywhere — just implement the method
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
// Circle now satisfies Shape automatically2. Small, Focused Interfaces
Go encourages tiny interfaces. The standard library's io.Reader and io.Writer each have exactly one method. Compare to ISP in SOLID.
interface ILogger {
void LogInfo(string msg);
void LogWarning(string msg);
void LogError(string msg);
}// Go idiom: small interfaces
type Writer interface {
Write(p []byte) (n int, err error)
}
type Reader interface {
Read(p []byte) (n int, err error)
}
// Compose when needed
type ReadWriter interface {
Reader
Writer
}3. Interface Variables and Type Assertions
An interface variable holds both the concrete value and its type. Use type assertions to retrieve the concrete type — like C# 'as' operator.
IAnimal a = new Dog();
if (a is Dog dog) {
Console.WriteLine("It's a dog: " + dog.Name);
}var a Animal = Dog{Name: "Rex"}
// Type assertion (panics if wrong type)
dog := a.(Dog)
// Safe assertion (like C# 'as')
if dog, ok := a.(Dog); ok {
fmt.Println("It's a dog:", dog.Name)
}4. Empty Interface and any
The empty interface interface{} has no methods, so every type satisfies it. Go 1.18 added 'any' as an alias — use any in modern code.
object anything = 42;
anything = "now a string";
// Must cast to use: (string)anythingvar anything any = 42
anything = "now a string"
// Type switch to handle multiple types
switch v := anything.(type) {
case int:
fmt.Println("int:", v)
case string:
fmt.Println("string:", v)
default:
fmt.Println("unknown type")
}Common Mistakes
When coming from C#, developers often make these mistakes:
- Go interfaces are satisfied implicitly — no 'class Dog : IAnimal' declaration needed
- If a type has all the methods, it automatically implements the interface
- This is called structural typing (duck typing at compile time)
Key Takeaways
- Go interfaces are satisfied implicitly — just implement the methods
- Prefer small, single-method interfaces (io.Reader pattern)
- Use the two-value type assertion 'v, ok := i.(T)' for safe type checks
- Use 'any' (alias for interface{}) only when truly needed