Introduction to Go Interfaces

Jonathan Seow
CodeX
Published in
9 min readApr 17, 2021
Hello Gophers! Photo by Lukáš Vaňátko on Unsplash.

When I first started programming in Go, I find Go interfaces a confusing topic to understand. After many hours of reading and researching, I have produced this article as an attempt to simplify Go interfaces for budding Gophers.

In this tutorial, I aim to answer the following questions with plain, simple English and childlike examples.

  1. What is a Go interface? How do you use it?
  2. Why is Go interface useful?
  3. How do interfaces work under the hood?

I assume you are familiar with basic Go syntaxes like functions and structs. Let’s get started! 🏃

What is a Go Interface?

In formal terms, a Go interface is a group of method signatures that a type can implement. Confused? Let us break down this statement into three questions to guide your understanding.

🤔 ️What is a type?

A type refers to a data type. In Go, every variable, function parameters, and return values must have a type. Go uses a minimalistic type system that is very easy to work with.

Some common data types in Go.

We can add behaviors to a Go data type by attaching functions to it. Functions related to a type are called methods. To demonstrate this idea, let us first define a Donald struct and two functions Quack and Walk.

package mainimport "fmt"type Donald struct {}func Quack() {
fmt.Println("I am Donald Duck!")
}
func Walk() {
fmt.Println("I waddle")
}
func main() {
Quack() // I am Donald Duck!
Walk() // I waddle
}

In this (boring) example, Quack and Walk are normal Go functions unrelated to Donald. To make them methods of Donald, we update the code as follows:

package mainimport "fmt"type Donald struct {}// Make Quack a method of Donald
func (d Donald) Quack() {
fmt.Println("I am Donald Duck!")
}
// Make Walk a method of Donald
func (d Donald) Walk() {
fmt.Println("I waddle")
}
func main() {
d:= new(Donald)
d.Quack()
d.Walk()
}

Notice how we added (d Donald) in front of Quack and Walk. This turns the functions into methods of Donald . To use the methods, we need to create an instance of Donald and call them via the dot operator.

Donald Duck. Photo by Kin Li on Unsplash.

🤔 What is a method signature?

A method signature is the identity of a method, just like the handwritten signature of a person. In Go, a method signature consists of the method name, return type, and its parameters.

For example, the method Quack above that takes in no arguments and returns nothing has a method signature as follows:

Quack()

The method Walk has a similar signature with a different method name.

Walk()

It is important to note that a method signature does not have a method body. In other words, it does not tell us anything about the underlying implementation of a method.

Now, we can finally create our first Go interface! Remember, an interface is just a collection of method signatures. We shall name our interface Duck with the signatures of Quack and Walk.

type Duck interface {
Quack()
Walk()
}
Photo by Josue Michel on Unsplash.

That’s all well and good, but the interface is not doing anything now. How do we use it? How does a type implement an interface?

🤔 How to implement an interface?

In Go, a data type is said to implement an interface if it has methods that satisfy the method signatures of that interface.

In other words, when a type provides the implementation of methods defined in an interface, along with the same parameters and return types, it has implemented the interface.

For instance, the Donald struct implements the Duck interface because it has two methods Quack and Walk that accept no arguments and return nothing.

💻 Go language: Donald implements the Duck interface.

🧍 Human language: Donald is a Duck!

type Duck interface {
Quack()
Walk()
}
// Donald implements the Duck interface
type Donald struct {}
func (d Donald) Quack() {
fmt.Println("I am Donald Duck!")
}
func (d Donald) Walk() {
fmt.Println("I waddle")
}

It is important to remember that a type implements an interface implicitly. There is no extra syntax or keywords needed. The only condition is that the type must have the methods of an interface.

Why use Go Interfaces?

In the previous section, I have explained what is a Go interface and the necessary syntax to use it. To recap, I created a struct Donald that implements the Duck interface with two methods, Quack and Walk.

To effectively use Go interfaces, we need to understand why and how an interface can be useful in our program. In essence, Go interfaces allow us to achieve what’s known as duck typing.

💡 Duck typing defines an object or type by WHAT IT CAN DO instead of what it actually is.

As usual, concepts like this can be made clearer with examples. Let’s define a new struct called Thor , based upon my favorite Marvel character!

type Thor struct {}

For those who don’t know who Thor is, he is the God of Thunder in the Marvel Cinematic Universe and Norse mythology.

Thor in Marvel.

As you can see, he is in no way, shape, or form, a duck. But thanks to Go interfaces, we can make him a duck! All we have to do is make Thor implements the Duck interface by giving him the Quack and Walk methods!

// Thor implements the Duck interface
type Thor struct {}
func (t Thor) Quack() {
fmt.Println("I am the God of Thunder!")
}
func (t Thor) Walk() {
fmt.Println("I fly and shoot lightning")
}

Because Thor can now quack and walk like a duck, he can be treated like a duck! This is the core idea behind duck typing: An object is defined by what it can do (the methods it has) instead of what it is (its type).

🦆 If it walks like a duck and quacks like a duck, then it must be a duck!

Okay… So I can make Thor a duck with Go interfaces. How does that help me in writing Go programs? Let us imagine you are working on an application that makes objects quack and walk like a duck.

To start simple, you create a struct called Donald like in the previous section with the methods Quack and Walk . Then, you create a normal function that takes in an argument of type Donald and call both the methods.

func behaveLikeDonald(d Donald) {
d.Quack()
d.Walk()
}

People love Donald and your application grows in popularity! One day, your users want Thor to behave like a duck. Simple enough, you repeat what you did with Donald .

func behaveLikeThor(t Thor) {
t.Quack()
t.Walk()
}

Now, your application gets better! More people start requesting different objects to behave like ducks. Suddenly, you realize your code is not scalable because you need to create a new function for each object!

One function for each object.

Wouldn’t it be great if you can create only one function to handle any objects that have both the Quack and Walk method? You guessed it, Go interfaces to the rescue!

In Go, interfaces can be treated as a type in function parameters and return values. In the example above, both Donald and Thor are of interface type Duck because they implement the interface (i.e. has methods of Duck).

With that, you only need one function that accepts an argument of interface type Duck . This makes your application scales easily with the number of Duck-related objects!

// Define function argument to be of type Duck
func behaveLikeDuck (d Duck) {
d.Quack()
d.Walk()
}

If you are familiar with Object-Oriented Programming (OOP), you can say that the function behaveLikeDuck has achieved polymorphism.

N objects with only one function.

In conclusion, Go interfaces allow us to create abstractions based on the methods that an input type has instead of its actual concrete type. This allows us to design abstractions that are flexible, extensible, and scalable!

If you still cannot see the benefits of Go interfaces, don’t worry too much about it yet. When you are starting in Go, there is rarely a need for you to design your own interfaces.

The main takeaway is the idea of using interfaces as data types because this is a common design pattern of Go abstractions. Always remember the following statement:

An interface type is concerned with what an object can do instead of what is actually is. In other words, duck-typing 🦆 .

Under the Hood with Interface Values

In the section above, I have given a simple example of how Go interfaces can be useful in designing powerful and reusable abstractions.

To recap, I created a custom struct Donald that implements a Duck interface with two methods Quack and Walk . There is also a function behaveLikeDuck that takes in an argument of interface type Duck .

func behaveLikeDuck (d Duck) {
d.Quack()
d.Walk()
}

Let us think about how Go interfaces work underneath the hood. To be specific, what happens when we call the function behaveLikeDuck ?

Hmmm. Photo by Markus Winkler on Unsplash.

When we pass a variable to behaveLikeDuck , Go performs static type checking during compilation to check if the variable satisfies the Duck interface. If it doesn’t, the compilation fails.

During runtime, Go performs type conversion and creates a local variable d of type Duck inside behaveLikeDuck . This raises a question: when the methods Quack and Walk are called, how does Go know which concrete implementation of the methods to execute?

❗ There could be many types that implement Duck , each with their own version of Quack and Walk .

The local variable d is an interface value. In the simplest sense, you can think of an interface value as a list with two pieces of information: the underlying data and the concrete type of the interface.

For example, if we provide a variable of type Donald , the underlying data of d points to the instance of Donald while the concrete type is the struct Donald with its method implementations.

Simplified representation of an interface value

An interface value also exposes a method for us to perform type assertions with its underlying concrete type. We won’t be going into that for now as this deserves a separate article.

When we call a method on the interface value d , Go looks for the method of the same name under the concrete type and executes it. In that sense, Go is dynamic enough to work with different but related objects at runtime.

One caveat that you must remember is that the methods you can access on an interface value are only those defined in the interface.

Back to the example above, if Donald has another method called Eat , you cannot call it with the interface value d because it is not defined in the Duck interface. Again, if you are familiar with OOP, this is consistent with how upcasting works in languages like Java.

Final Thoughts

That wraps up an introduction to Go interfaces for Go beginners! In this article, I have explained what Go interfaces are, why are they useful, the underlying implementation of Go interfaces, and duck typing.

There is a lot more to talk about Go interfaces, but I shall leave them to my future articles since this is running a bit long.

Thank you for reading. Peace ✌️!

--

--

CodeX
CodeX

Published in CodeX

Everything connected with Tech & Code. Follow to join our 1M+ monthly readers

Jonathan Seow
Jonathan Seow

Written by Jonathan Seow

Software Engineer @ TikTok I also share byte-sized coding insights on my blog! https://www.thebytearray.com/