interface
| Since: | Go 1.0(2012) |
|---|
An interface defines a set of methods that a type must implement. In Go, interfaces are implemented implicitly — no explicit declaration is required.
Syntax
type InterfaceName interface {
MethodName(params) ReturnType
}
// The empty interface (any) accepts all types.
var v any = "Hello"
var v interface{} = 42
Interface overview
| Syntax / Type | Description |
|---|---|
| type I interface { M() } | Defines an interface that requires the method M(). |
| any | A built-in alias for interface{} (Go 1.18+). Accepts all types. |
| interface{} | An empty interface with zero methods. Every type satisfies it. |
| Type assertion: v.(T) | Converts an interface value to a concrete type. Panics if the conversion fails. |
| Type assertion: v, ok := v.(T) | A safe form of type assertion. If the conversion fails, ok is false and no panic occurs. |
| Type switch: switch v.(type) | Branches execution based on the concrete type of an interface value. |
Sample code
interface.go
package main
import (
"fmt"
"math"
)
// Define the Shape interface.
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
// Circle implicitly implements the Shape interface.
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
type Rectangle struct {
Width, Height float64
}
// Rectangle also implements the Shape interface.
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// A function that accepts the Shape interface as a parameter.
func printShape(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
c := Circle{Radius: 5}
r := Rectangle{Width: 3, Height: 4}
printShape(c) // Circle satisfies Shape.
printShape(r) // Rectangle also satisfies Shape.
// The empty interface accepts all types.
var anything any = "hello"
fmt.Println(anything) // Prints "hello".
anything = 123
fmt.Println(anything) // Prints "123".
// Use a type assertion to convert to a concrete type.
var s Shape = Circle{Radius: 3}
if circle, ok := s.(Circle); ok {
fmt.Println("Radius:", circle.Radius) // Prints "Radius: 3".
}
// Use a type switch to branch by type.
values := []any{42, "hello", true, 3.14}
for _, v := range values {
switch val := v.(type) {
case int:
fmt.Printf("int: %d\n", val)
case string:
fmt.Printf("string: %s\n", val)
default:
fmt.Printf("other: %v\n", val)
}
}
}
This produces the following output:
go run interface.go Area: 78.54, Perimeter: 31.42 Area: 12.00, Perimeter: 14.00 hello 123 Radius: 3 int: 42 string: hello other: true other: 3.14
How implicit implementation works
Go interfaces are implemented implicitly. Unlike Java or Kotlin, you never write implements InterfaceName. A type automatically satisfies an interface simply by defining all the methods it requires. This is called "duck typing."
This design lets you define an interface and its implementations in separate packages. You can also retrofit an existing type to satisfy an interface you define later.
sample_implicit_interface.go
package main
import "fmt"
// Stringer interface (the fmt package defines a similar one)
type Stringer interface {
String() string
}
// This struct does not need to mention Stringer when it is defined
type Character struct {
Name string
Power int
}
// Defining String() automatically satisfies the Stringer interface
func (c Character) String() string {
return fmt.Sprintf("%s (Power: %d)", c.Name, c.Power)
}
// Accepts anything that satisfies Stringer
func printAll(items []Stringer) {
for _, item := range items {
fmt.Println(item.String())
}
}
// A common idiom to verify interface satisfaction at compile time
// var _ Stringer = Character{} // Compile error if Character does not satisfy Stringer
func main() {
chars := []Stringer{
Character{Name: "Kiryu Kazuma", Power: 95},
Character{Name: "Majima Goro", Power: 92},
}
printAll(chars)
}
This produces the following output:
go run sample_implicit_interface.go Kiryu Kazuma (Power: 95) Majima Goro (Power: 92)
Pitfalls of the empty interface
The empty interface (any / interface{}) accepts every type, but overusing it erodes type safety. Defining a specific interface where possible keeps your code more robust.
Less ideal: accepts anything but loses type information.
sample_any_bad.go
package main
import "fmt"
func printAnything(v any) {
fmt.Printf("value: %v, type: %T\n", v, v)
}
func main() {
var v any = "Kiryu"
if s, ok := v.(string); ok {
fmt.Println("string:", s)
}
if n, ok := v.(int); ok {
fmt.Println("int:", n)
} else {
fmt.Println("not an int")
}
}
This produces the following output:
go run sample_any_bad.go string: Kiryu not an int
Better: Define an interface that requires only the methods you need.
sample_any_interface.go
package main
import "fmt"
type Displayable interface {
Display() string
}
type Item struct {
Name string
Price int
}
func (it Item) Display() string {
return fmt.Sprintf("%s: $%d", it.Name, it.Price)
}
func printDisplayable(d Displayable) {
fmt.Println(d.Display())
}
func main() {
printDisplayable(Item{Name: "Kiryu's Suit", Price: 500})
}
This produces the following output:
go run sample_any_interface.go Kiryu's Suit: $500
Common Mistakes
Common mistake 1: pointer receiver constraint
A method defined with a pointer receiver cannot be satisfied by a value type. Only the pointer type satisfies the interface.
package main
type Animal interface {
Sound() string
}
type Cat struct{ Name string }
// Pointer receiver: *Cat satisfies Animal, but Cat does not
func (c *Cat) Sound() string { return "Meow" }
func makeSound(a Animal) {}
func main() {
// makeSound(Cat{Name: "Tama"}) // Compile error: Cat does not satisfy Animal
}
OK: Pass a pointer to satisfy the interface.
sample_interface_ok.go
package main
import "fmt"
type Animal interface {
Sound() string
}
type Dog struct{ Name string }
func (d Dog) Sound() string { return "Woof" }
type Cat struct{ Name string }
func (c *Cat) Sound() string { return "Meow" }
func makeSound(a Animal) {
fmt.Println(a.Sound())
}
func main() {
// Dog uses a value receiver, so both Dog and *Dog satisfy Animal
makeSound(Dog{Name: "Hachiko"})
// Cat uses a pointer receiver, so only *Cat satisfies Animal
makeSound(&Cat{Name: "Tama"}) // pass a pointer
}
This produces the following output:
go run sample_interface_ok.go Woof Meow
Common mistake 2: nil interface pitfall
A nil interface and an interface holding a nil pointer are not the same. An interface variable that stores a typed nil pointer is not nil itself.
sample_interface_nil.go
package main
import "fmt"
type Animal interface {
Sound() string
}
type Dog struct{ Name string }
func (d *Dog) Sound() string { return "Woof" }
func main() {
var a Animal = nil // The interface itself is nil
var d *Dog = nil // A nil pointer — still has type information
var a2 Animal = d // An interface holding a nil pointer
fmt.Println(a == nil) // true: a is a true nil interface
fmt.Println(a2 == nil) // false: a2 carries type info, so it is NOT nil!
}
This produces the following output:
go run sample_interface_nil.go true false
Notes
Unlike many other languages, Go does not require a type to explicitly declare that it implements an interface. If a type implements all the methods an interface requires, it satisfies that interface automatically. This is known as "duck typing" — a mechanism where a type is considered to satisfy an interface simply by having the required methods, without any explicit declaration.
The type assertion v.(T) panics if the conversion fails. To convert safely, use the two-value form v, ok := v.(T).
The empty interface any (formerly interface{}) can hold any type, but you must use a type assertion or type switch to work with the underlying value. Defining a specific interface rather than relying on any keeps your code type-safe.
If you find any errors or copyright issues, please contact us.