Go interface
last modified May 3, 2025
In this article we show how to work with interfaces in Golang.
An interface in Go is a specific type that defines a set of function signatures without specifying their implementation. Unlike languages such as Java or C#, where interfaces require explicit declarations, Go follows an implicit approach: a type satisfies an interface simply by implementing its functions.
The primary role of an interface is to define behavior through function signatures. Each function signature specifies a name, input arguments, and return types, but the actual implementation is left to the types that satisfy the interface. This design promotes flexibility and decoupling, making Go code more modular and adaptable.
Interfaces are often referred to as exposed APIs or contracts. If a type implements the functions defined in an interface—such as a sortable interface—it adheres to the contract, guaranteeing that the type supports sorting operations. This allows developers to write generic and reusable code without needing to know the specific underlying type.
Since interfaces in Go specify behavior rather than implementation, they play a crucial role in enabling polymorphism. They allow different types to be used interchangeably in functions or data structures that expect a given interface, improving code extensibility and maintainability.
Using interfaces can make code clearer, shorter, and more readable.
Go interface example
The following example uses a simple Shape
interface.
package main import ( "fmt" "math" ) type Shape interface { Area() float64 } type Rectangle struct { Width, Height float64 } type Circle struct { Radius float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius } func getArea(shape Shape) { fmt.Println(shape.Area()) } func main() { r := Rectangle{Width: 7, Height: 8} c := Circle{Radius: 5} getArea(r) getArea(c) }
The Shape
is a generic geometric form. It cannot be drawn. The
Rectangle
and Circle
are specific geometric forms,
which can be drawn and for which we can calculate the area.
type Shape interface { Area() float64 }
We define the Shape
interface. It has one function signature:
Area
.
type Rectangle struct { Width, Height float64 } type Circle struct { Radius float64 }
We define two types: the Rectangle
and the Circle
.
func (r Rectangle) Area() float64 { return r.Width * r.Height } func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
We define the Area
function for the Rectangle
and
Circle
; we say that these two types implement the Shape
interface.
func getArea(shape Shape) { fmt.Println(shape.Area()) }
The getArea
function takes the Shape
interface as a
parameter. We can pass both the Rectangle
and the Circle
,
because they are both shapes.
$ go run geo_shapes.go 56 78.53981633974483
Go interface slice
In the following example, we create a slice of Animal
interface.
package main import ( "fmt" ) type Animal interface { Sound() string } type Dog struct { } func (d Dog) Sound() string { return "Woof!" } type Cat struct { } func (c Cat) Sound() string { return "Meow!" } type Cow struct { } func (l Cow) Sound() string { return "Moo!" } func main() { animals := []Animal{Dog{}, Cat{}, Cow{}} for _, animal := range animals { fmt.Println(animal.Sound()) } }
The example defines an Animal
interface and Dog
,
Cat
, and Cow
types.
type Animal interface { Sound() string } type Dog struct { } func (d Dog) Sound() string { return "Woof!" }
The Dog
type implements the Sound
contract function of
the Animal
interface.
animals := []Animal{Dog{}, Cat{}, Cow{}} for _, animal := range animals { fmt.Println(animal.Sound()) }
Because all three types implement one common interface, we can place them into a slice.
$ go run interface_slice.go Woof! Meow! Moo!
Go Stringer interface
The Stringer
interface is defined in the fmt
package.
Its String
function is invoked when a type is passed to any of the
print functions. We can customize the output message of our own types.
type Stringer interface { String() string }
This is the Stringer
interface.
package main import ( "fmt" ) type User struct { Name string Occupation string } func (u User) String() string { return fmt.Sprintf("%s is a(n) %s", u.Name, u.Occupation) } func main() { u1 := User{"John Doe", "gardener"} u2 := User{"Roger Roe", "driver"} fmt.Println(u1) fmt.Println(u2) }
In the code example, we define the String
function of the
Stringer
interface for the User
type.
func (u User) String() string { return fmt.Sprintf("%s is a(n) %s", u.Name, u.Occupation) }
The implementation returns a string that tells the user's name and occupation.
$ go run stringer.go John Doe is a(n) gardener Roger Roe is a(n) driver
Go interface{}
Go interface{}
is an empty interface; all types in Go satisfy
the empty interface. Any type can be assigned to a variable declared with
empty interface.
package main import ( "fmt" ) type Body struct { Msg interface{} } func main() { b := Body{"Hello there"} fmt.Printf("%#v %T\n", b.Msg, b.Msg) b.Msg = 5 fmt.Printf("%#v %T\n", b.Msg, b.Msg) }
We have the Msg
variable of interface{}
. In the
example, we assign a string and an integer to the variable.
$ go run empty_interface.go "Hello there" string 5 int
Go type assertion
The type assertion x.(T)
asserts that the concrete value stored in
x
is of type T
, and that x
is not
nil
.
package main import ( "fmt" ) func main() { var val interface{} = "falcon" r, ok := val.(string) fmt.Println(r, ok) r2, ok2 := val.(int) fmt.Println(r2, ok2) r3 := val.(string) fmt.Println(r3) //r4 := val.(int) //fmt.Println(r4) }
In the code example, we check the type of the val
variable.
r, ok := val.(string) fmt.Println(r, ok)
In the r
variable, we have the value. The ok
is a
boolean that tells if the value is the string type.
//r4 := val.(int) //fmt.Println(r4)
The commented lines would lead to a panic:
interface conversion: interface {} is string, not int
.
$ go run type_assertion.go falcon true 0 false falcon
In the following example, we have a map with string
type keys and
interface{}
for values. It allows us to use various types for
the values.
package main import ( "fmt" "log" ) func main() { user := make(map[string]interface{}, 0) user["name"] = "John Doe" user["age"] = 21 user["weight"] = 70.3 age, ok := user["age"].(int) if !ok { log.Fatal("assert failed") } user["age"] = age + 1 fmt.Printf("%+v", user) }
Since the values are defined as empty interface, the underlying type of the age is lost. We need to cast the value to int in order to increment it.
Go type switch
A type switch is used to compare the concrete type of an interface with the multiple types provide in the case statements.
package main import "fmt" type User struct { Name string } func checkType(a interface{}) { switch a.(type) { case int: fmt.Println("Type: int, Value:", a.(int)) case string: fmt.Println("Type: string, Value:", a.(string)) case float64: fmt.Println("Type: float64, Value:", a.(float64)) case User: fmt.Println("Type: User, Value:", a.(User)) default: fmt.Println("unknown type") } } func main() { checkType(4) checkType("falcon") checkType(User{"John Doe"}) checkType(7.9) checkType(true) }
The checkType
function determines the type of its parameter in a
switch statement.
$ go run type_switch.go Type: int, Value: 4 Type: string, Value: falcon Type: User, Value: {John Doe} Type: float64, Value: 7.9 unknown type
Source
The Go Programming Language Specification
In this article we have covered the interface type in Golang.
Author
List all Go tutorials.