Skip to content

Intermediate · Pointers, interfaces and errors · topic 17 of 46 · 40 min

Interfaces: behaviour, not family trees

Explanation

An interface is a type that lists methods. Any type that has those methods satisfies the interface automatically; you never write "implements". Small interfaces like io.Reader, io.Writer, fmt.Stringer and error are the heart of Go. Write functions that accept interfaces, so they work with many types and are easy to test.

The problem interfaces solve

Imagine a checkout that can charge by UPI, card or wallet. Without interfaces you would write a separate checkout for each, or a big switch. An interface lets you say: "I accept anything that can Pay(amount)". Each payment type implements Pay in its own way, and the checkout code never changes when you add a new one.

go
type PaymentMethod interface {
	Pay(paise int64) error
}

type UPI struct{ VPA string }
type Card struct{ Last4 string }

func (u UPI) Pay(p int64) error {
	fmt.Println("UPI", u.VPA, "paid", p)
	return nil
}

func (c Card) Pay(p int64) error {
	fmt.Println("Card", c.Last4, "paid", p)
	return nil
}

func checkout(m PaymentMethod, total int64) error {
	return m.Pay(total)
}

func main() {
	checkout(UPI{VPA: "asha@okbank"}, 49900)
	checkout(Card{Last4: "4242"}, 120000)
}
UPI and Card never mention PaymentMethod. Having a Pay(int64) error method is enough.

Implicit satisfaction

In Java you write class UPI implements PaymentMethod. Go has no such keyword. A type satisfies an interface just by having all its methods with the exact signatures. This means you can define an interface later, in the package that USES it, for types you did not write.

Think of it like this: A plug socket does not care which company made your phone charger. If the plug has the right pins, it fits. Interfaces are the socket: "anything with these methods fits here".

Small interfaces from the standard library

InterfaceMethodUsed by
fmt.StringerString() stringfmt.Println prints your type nicely
errorError() stringevery error value in Go
io.ReaderRead(p []byte) (n int, err error)files, HTTP bodies, network connections, strings.Reader
io.WriterWrite(p []byte) (n int, err error)files, os.Stdout, http.ResponseWriter, bytes.Buffer
sort.InterfaceLen, Less, Swapsort.Sort (older code)

Because io.Writer is tiny, one function like fmt.Fprintf(w io.Writer, ...) can write to the screen, a file, an HTTP response or a buffer in memory. That is the power of small interfaces: "the bigger the interface, the weaker the abstraction" is a well-known Go proverb.

go
type Rupees int64

func (r Rupees) String() string {
	return fmt.Sprintf("Rs %d.%02d", r/100, r%100)
}

func main() {
	var price Rupees = 49950
	fmt.Println(price) // Rs 499.50
}
Adding a String method makes your type a fmt.Stringer, and fmt uses it automatically.

Pointer receivers and interfaces

If a method has a pointer receiver, only the pointer type has that method in its method set. So *T satisfies the interface but T does not. The compiler tells you clearly.

go
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }

type Incrementer interface{ Inc() }

var _ Incrementer = &Counter{}  // OK
// var _ Incrementer = Counter{} // compile error: Counter does not implement
//                               // Incrementer (method Inc has pointer receiver)
var _ Interface = value is also a handy compile-time check that a type satisfies an interface.

Interfaces make testing easy

If your code depends on an interface instead of a concrete database or payment gateway, a test can pass in a fake that records calls. No real money moves and no network is needed. This is why Go developers say: accept interfaces, return concrete types (structs).

go
type fakePay struct{ calls int }

func (f *fakePay) Pay(p int64) error { f.calls++; return nil }

// in a test:
// f := &fakePay{}
// checkout(f, 100)
// if f.calls != 1 { t.Fatal("expected one payment") }
A fake that satisfies PaymentMethod in four lines.

Design tips and mistakes

  • Keep interfaces small: one to three methods is normal.
  • Define the interface where it is used (the consumer), not next to the type that implements it.
  • Do not create an interface for every struct "just in case". Add one when you have two implementations, or need a fake for tests.
  • Interface names for single methods often end in -er: Reader, Writer, Stringer, Notifier.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Interfaces: behaviour, not family trees · 1/7

Interfaces

Interfaces describe behaviour: a list of methods a type must have.

Prefer a video by a teacher? Find free videos on “golang interfaces explained implicit implementation io.Reader io.Writer” on YouTube ↗

In real life: Notifications by SMS, email or WhatsApp

A bank sends OTP and alert messages by several channels. The alert code depends only on a Notifier interface with one method. New channels are added as new types, and tests use a fake notifier that just stores messages in a slice, so no real SMS is sent during testing.

go
package main

import "fmt"

type Notifier interface {
	Notify(to, msg string) error
}

type SMS struct{}
type Email struct{ From string }

func (SMS) Notify(to, msg string) error {
	fmt.Println("SMS to", to+":", msg)
	return nil
}

func (e Email) Notify(to, msg string) error {
	fmt.Println("Email from", e.From, "to", to+":", msg)
	return nil
}

func alertAll(n Notifier, users []string, msg string) {
	for _, u := range users {
		if err := n.Notify(u, msg); err != nil {
			fmt.Println("failed for", u, err)
		}
	}
}

func main() {
	alertAll(SMS{}, []string{"98XXXXXX10"}, "Rs 2,000 debited")
	alertAll(Email{From: "alerts@bank.example"}, []string{"asha@example.com"}, "Statement ready")
}
alertAll never changes when a WhatsApp notifier is added later.

Practice (0/4 done)

Task 1 · Question

type Shape interface{ Area() float64 }. Which type satisfies Shape?

Task 2 · Question

What does this print? type Temp float64 func (t Temp) String() string { return fmt.Sprintf("%.1f°C", float64(t)) } ... fmt.Println(Temp(36.6))

Task 3 · Question

Why does this not compile? type Saver interface{ Save() } type Doc struct{} func (d *Doc) Save() {} var s Saver = Doc{}

Task 4 · Practice

Define an interface Shape with Area() float64 and Perimeter() float64. Implement it for Rect and Circle (use math.Pi). Write func describe(s Shape) that prints both values with 2 decimals, and call it with a slice of mixed shapes.

Quick quiz

Question 1

1.How does a type declare that it implements an interface?

Question 2

2.Which method makes a type a fmt.Stringer?

Question 3

3.What does "accept interfaces, return structs" mean?

Question 4

4.Which interface does os.Stdout, a file and http.ResponseWriter all satisfy?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

How does a type declare that it implements an interface?

Answer: It does not; having the methods is enough

Go interfaces are satisfied implicitly.

Which method makes a type a fmt.Stringer?

Answer: String() string

fmt.Stringer has one method: String() string.

What does "accept interfaces, return structs" mean?

Answer: Functions take small interfaces as parameters and return concrete types

Taking interfaces makes code flexible and testable; returning concrete types keeps it clear.

Which interface does os.Stdout, a file and http.ResponseWriter all satisfy?

Answer: io.Writer

They all have Write([]byte) (int, error).

How many interfaces can a class implement?Related

Answer: Many

Multiple interface implementation is allowed.

From Interfaces

Which statement about implicit interfaces is true?Related

Answer: Every class defines an interface other classes can implement

Any class can be implemented.

From Inheritance, interfaces, mixins and extensions

When should a method use a pointer receiver?Related

Answer: When it must change the receiver or the struct is large

Pointer receivers let methods modify the original and avoid copying big structs.

From Structs and methods

When is an interface value equal to nil?Related

Answer: When both its type and value parts are unset

A typed nil pointer inside an interface still makes the interface non-nil.

From any, type assertions and type switches

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Pointers, interfaces and errors” before its exam: any, type assertions and type switches, Errors as values: wrap, Is and As, panic and recover: only for the truly unexpected.
  2. Module 4 exam: Pointers, interfaces and errors · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Packages, testing and generics. 46 topics left in the course before the final exam.

← Previous: Pointers without fear