Methods and Interfaces
Methods and Interfaces in Go
Method receivers
Methods are defined with a receiver — a parameter between func and the method name:
func (r Rectangle) Area() float64 { … } // value receiver
func (c *Counter) Increment() { c.n++ } // pointer receiver- Value receiver: receives a copy. Use when the method doesn't modify state.
- Pointer receiver: receives a pointer to the original. Use when modifying state or for large structs.
Interfaces
An interface specifies a set of method signatures. A type implements an interface implicitly — no implements keyword needed:
type Stringer interface { String() string }Any type with a String() string method satisfies Stringer.
Empty interface interface{} (or any)
The empty interface is satisfied by every type — similar to Object in other languages.
Type assertions Extract the concrete value from an interface:
s, ok := val.(string)Key points:
- Interfaces enable polymorphism and dependency inversion in Go.
- Prefer small, focused interfaces (often one or two methods).
- The standard library's
io.Reader,io.Writer, andfmt.Stringerare canonical examples.
Code Examples
package main
import "fmt"
type Counter struct {
count int
}
func (c Counter) Value() int {
return c.count
}
func (c *Counter) Increment() {
c.count++
}
func (c *Counter) Reset() {
c.count = 0
}
func main() {
c := Counter{}
c.Increment()
c.Increment()
c.Increment()
fmt.Println("Count:", c.Value())
c.Reset()
fmt.Println("After reset:", c.Value())
}Pointer receivers modify the original struct. Value receivers operate on a copy and are fine for read-only methods.
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
type Rect struct{ W, H float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func (r Rect) Perimeter() float64 { return 2 * (r.W + r.H) }
func printShape(s Shape) {
fmt.Printf("%T — Area: %.2f, Perimeter: %.2f\n", s, s.Area(), s.Perimeter())
}
func main() {
shapes := []Shape{Circle{5}, Rect{4, 6}}
for _, s := range shapes {
printShape(s)
if c, ok := s.(Circle); ok {
fmt.Printf(" Radius: %.1f\n", c.Radius)
}
}
}Both types implicitly satisfy Shape. Type assertions extract the concrete type; the ok form is safe and avoids panics.
Quick Quiz
1. How does a type implement an interface in Go?
2. When should you use a pointer receiver instead of a value receiver?
Was this lesson helpful?