Go Tutorial: Go Interfaces

These three concepts are confusing initially because a method looks almost exactly like a function. The easiest way is to learn them in this order:

Function → Struct → Method → Interface

Forget interfaces for a moment.

1. Function = a standalone piece of work

You already know this:

package main

import "fmt"

func add(a int, b int) int {
    return a + b
}

func main() {
    result := add(10, 20)
    fmt.Println(result)
}

Here:

func add(a int, b int) int

means:

func     add       (a int, b int)     int
 ↓        ↓              ↓             ↓
function name         inputs       return type

And you call it directly:

add(10, 20)

Think:

Function = independent action.


2. Struct = an object/data type

Now suppose we’re making a Person.

type Person struct {
    Name string
    Age  int
}

Then:

p := Person{
    Name: "Rajesh",
    Age:  42,
}

We have:

Person
 ├── Name
 └── Age

But currently the Person only has data.

What if we want the Person to perform an action?

That’s where a method comes in.


3. Method = function attached to a type

Example:

package main

import "fmt"

type Person struct {
    Name string
}

func (p Person) sayHello() {
    fmt.Println("Hello, my name is", p.Name)
}

func main() {
    p := Person{Name: "Rajesh"}

    p.sayHello()
}

Output:

Hello, my name is Rajesh

Look carefully at this:

func (p Person) sayHello() {

Compare it with a normal function:

func sayHello() {

The only important difference is this:

(p Person)

That’s called the receiver.

It means:

sayHello() belongs to Person.

Therefore we call it like:

p.sayHello()

rather than:

sayHello()

4. Function vs Method

This is the key distinction.

Function

func add(a int, b int) int {
    return a + b
}

Call:

add(10, 20)

Method

func (p Person) sayHello() {
    fmt.Println(p.Name)
}

Call:

p.sayHello()

So:

FUNCTION
────────

add(10, 20)
 ↑
standalone


METHOD
──────

p.sayHello()
↑    ↑
|    method
|
Person object

The easiest rule:

A method is basically a function attached to a type.


5. One example with both Function and Method

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

// Normal function
func add(a int, b int) int {
    return a + b
}

// Method of Person
func (p Person) introduce() {
    fmt.Println("My name is", p.Name)
}

func main() {

    result := add(10, 20)
    fmt.Println(result)

    p := Person{
        Name: "Rajesh",
        Age:  42,
    }

    p.introduce()
}

There are now two different things:

add()
  │
  └── normal function


Person
  │
  └── introduce()
        │
        └── method

6. Why use methods at all?

Imagine:

type BankAccount struct {
    Balance float64
}

Actions related to a bank account naturally belong to the account:

account.Deposit(1000)
account.Withdraw(500)
account.ShowBalance()

Instead of writing:

deposit(account, 1000)
withdraw(account, 500)
showBalance(account)

Go lets us associate behavior with the type.

Example:

package main

import "fmt"

type BankAccount struct {
    Balance float64
}

func (a BankAccount) showBalance() {
    fmt.Println("Balance:", a.Balance)
}

func main() {

    account := BankAccount{
        Balance: 5000,
    }

    account.showBalance()
}

Think:

BankAccount
     │
     ├── Balance      ← data
     │
     └── showBalance  ← behavior/method

Now methods should hopefully make sense.


7. NOW interface

Interface sounds complicated, but its basic idea is simple:

An interface says which methods something must have.

It doesn’t contain the actual implementation.

For example:

type Speaker interface {
    Speak()
}

This says:

Anything having a Speak() method can be considered a Speaker.

That’s it.


10 practical use cases of interfaces in Go

#Use caseWhy use an interface?
1Payment methodsSupport Credit Card, UPI, PayPal, etc. through the same API
2Database / RepositorySwitch MySQL, PostgreSQL, memory storage, etc. without changing business logic
3LoggingSwitch console, file, Datadog, CloudWatch, etc.
4NotificationsHandle Email, SMS, Push notifications uniformly
5File/Storage systemsSwitch local disk, S3, GCS, Azure Blob
6Testing / MockingReplace real external systems with fake implementations
7Authentication providersSupport Google, GitHub, LDAP, Keycloak, etc.
8Message queuesSwitch Kafka, RabbitMQ, SQS, etc.
9Different shapes/types with common behaviorCircle, Rectangle, Triangle can all implement Area()
10Standard-library interoperabilityImplement interfaces such as io.Reader, io.Writer, error, fmt.Stringer

Interface → WHAT you can do
Struct → WHAT you are / what data you have
Method → HOW you do something

API Gateway

Provides one common entry point

Different backend services

Go Interface

Provides one common contract

Different implementations

8. Simple interface example

Let’s create a Dog:

type Dog struct {
    Name string
}

Give Dog a method:

func (d Dog) Speak() {
    fmt.Println("Woof!")
}

Now create an interface:

type Speaker interface {
    Speak()
}

Dog has:

Speak()

and Speaker requires:

Speak()

Therefore:

Dog satisfies Speaker

You don’t need to write:

Dog implements Speaker

anywhere.

Go figures it out automatically.


9. Complete example

package main

import "fmt"

type Speaker interface {
    Speak()
}

type Dog struct {
    Name string
}

func (d Dog) Speak() {
    fmt.Println(d.Name, "says Woof!")
}

func main() {

    dog := Dog{
        Name: "Tommy",
    }

    dog.Speak()
}

So far the interface isn’t doing anything useful.

Now watch this.


10. Another type can implement the same interface

Create a Person:

type Person struct {
    Name string
}

func (p Person) Speak() {
    fmt.Println(p.Name, "says Hello!")
}

Now:

Dog
 └── Speak()

Person
 └── Speak()

Both have Speak().

Therefore both satisfy:

type Speaker interface {
    Speak()
}

11. This is where interface becomes useful

Create a function:

func makeSpeak(s Speaker) {
    s.Speak()
}

Notice:

s Speaker

The function doesn’t ask for:

Dog

and doesn’t ask for:

Person

It says:

Give me anything capable of speaking.

Complete program:

package main

import "fmt"

type Speaker interface {
    Speak()
}

type Dog struct {
    Name string
}

func (d Dog) Speak() {
    fmt.Println(d.Name, "says Woof!")
}

type Person struct {
    Name string
}

func (p Person) Speak() {
    fmt.Println(p.Name, "says Hello!")
}

func makeSpeak(s Speaker) {
    s.Speak()
}

func main() {

    dog := Dog{Name: "Tommy"}
    person := Person{Name: "Rajesh"}

    makeSpeak(dog)
    makeSpeak(person)
}

Output:

Tommy says Woof!
Rajesh says Hello!

This is the important part:

makeSpeak(dog)
makeSpeak(person)

Same function accepts completely different types.

Why?

Because both have:

Speak()

12. Visualize the relationship

                Speaker interface
                ┌──────────────┐
                │   Speak()    │
                └──────┬───────┘
                       │
             requires Speak()
                       │
             ┌─────────┴─────────┐
             │                   │
            Dog                Person
             │                   │
         Speak()             Speak()
             │                   │
          "Woof"              "Hello"

The interface doesn’t care how they speak.

It only cares:

Do you have Speak()?

If yes → accepted.


13. Real-world analogy

Think about a USB port.

The USB port defines a contract:

USB Device
    │
    └── connect()

Many devices can satisfy it:

Keyboard ──→ connect()
Mouse    ──→ connect()
Camera   ──→ connect()
Drive    ──→ connect()

The computer doesn’t care whether it’s:

keyboard
mouse
camera
disk

It cares that it follows the required interface.

Go interfaces work similarly.


14. Function, Method and Interface together

Here’s the mental model I recommend memorizing:

FUNCTION
========

func add(a, b int) int

Called:

add(10, 20)

Meaning:
"Do some work"


METHOD
======

func (p Person) Speak()

Called:

p.Speak()

Meaning:
"Person can do this"


INTERFACE
=========

type Speaker interface {
    Speak()
}

Meaning:
"I accept anything that can Speak()"

That’s essentially the entire relationship.


The easiest way to understand an interface is to see the same problem without and with an interface.

Suppose we have an application that sends notifications.

Without Interface

We create an email sender:

package main

import "fmt"

type Email struct{}

func (e Email) Send(message string) {
	fmt.Println("Email:", message)
}

func Notify(email Email, message string) {
	email.Send(message)
}

func main() {
	email := Email{}

	Notify(email, "Server is down")
}

Notice this:

func Notify(email Email, message string)

Notify() specifically requires an Email.

Now suppose tomorrow we introduce SMS:

type SMS struct{}

func (s SMS) Send(message string) {
	fmt.Println("SMS:", message)
}

We cannot do this:

sms := SMS{}

Notify(sms, "Server is down") // ❌ ERROR

Why?

Because Notify() says:

func Notify(email Email, message string)

It requires specifically:

Email

not SMS.

So we may start creating separate functions:

func NotifyEmail(email Email, message string) {
	email.Send(message)
}

func NotifySMS(sms SMS, message string) {
	sms.Send(message)
}

Later:

Email
SMS
WhatsApp
Slack
Push Notification

Our code becomes increasingly tied to specific types.


With Interface

Now let’s define the behavior we actually need:

type Sender interface {
	Send(message string)
}

This says:

Give me anything that has a Send(string) method.

Complete example:

package main

import "fmt"

// Interface defines the required behavior.
type Sender interface {
	Send(message string)
}

// Email implementation
type Email struct{}

func (e Email) Send(message string) {
	fmt.Println("Email:", message)
}

// SMS implementation
type SMS struct{}

func (s SMS) Send(message string) {
	fmt.Println("SMS:", message)
}

// Notify does NOT care whether it receives
// Email, SMS, WhatsApp, etc.
//
// It only cares that the value can Send().
func Notify(sender Sender, message string) {
	sender.Send(message)
}

func main() {

	email := Email{}
	sms := SMS{}

	Notify(email, "Server is down")
	Notify(sms, "Server is down")
}

Output:

Email: Server is down
SMS: Server is down

The important change is just this:

Without interface

func Notify(sender Email, message string)

Means:

Give me an Email.

With interface

func Notify(sender Sender, message string)

Means:

Give me anything that can Send().

That’s the heart of interfaces.

WITHOUT INTERFACE

Notify()
   │
   │ requires exact type
   ↓
 Email


WITH INTERFACE

             Sender
          Send(string)
              │
       ┌──────┼──────┐
       ↓      ↓      ↓
     Email   SMS   WhatsApp
       │      │      │
       └──────┼──────┘
              ↓
           Notify()
Without interfaceWith interface
Depends on exact typeDepends on behavior
Notify(Email)Notify(Sender)
Only Email worksAny Sender works
Harder to extendEasier to extend
More tightly coupledMore flexible
SMS cannot be passedSMS can be passed if it has Send()

One sentence to remember

Without interface:

“I need an Email.”

With interface:

“I don’t care whether you’re Email, SMS, or something else — if you can Send(), I can use you.”

That is probably the simplest mental model for Go interfaces.

15. Look at the syntax side-by-side

Function

func hello() {
}

Method

func (p Person) hello() {
}

See the extra:

(p Person)

That’s what turns the function into a method associated with Person.

Interface

type Speaker interface {
    Speak()
}

Notice there is no implementation:

Speak()

not:

func Speak() {
    // implementation
}

An interface describes what must exist, not how to do it.


16. One sentence for each

Memorize these:

Function

A function performs some work.

add(10, 20)

Method

A method is a function attached to a type.

person.Speak()

Interface

An interface defines methods that a type must provide.

type Speaker interface {
    Speak()
}

17. Don’t learn interfaces too deeply yet

At your current stage, I would learn Go in this sequence:

1. Variables
      ↓
2. if / switch
      ↓
3. for / range
      ↓
4. Functions
      ↓
5. Struct
      ↓
6. Methods
      ↓
7. Pointers
      ↓
8. Interfaces
      ↓
9. Error handling
      ↓
10. Goroutines / Channels

In particular, don’t try to master interfaces before structs and methods are comfortable.

For now, the most important relationship is:

STRUCT
Person
   │
   │ gets behavior through
   ↓
METHOD
Speak()
   │
   │ method can satisfy
   ↓
INTERFACE
Speaker

And the single most important Go interface rule is:

If your type has all the methods required by an interface,
it automatically satisfies that interface.

No implements keyword is needed in Go.

1. Your First Go Interface

package main

import "fmt"

// Speaker is an interface.
//
// An interface describes BEHAVIOR.
//
// Speaker says:
// "Any type that has a Speak() string method
// can be used as a Speaker."
type Speaker interface {
	Speak() string
}

// Dog is a normal concrete struct type.
type Dog struct {
	Name string
}

// Speak is a method on Dog.
//
// Because this method has exactly the same signature
// required by Speaker:
//
//	Speak() string
//
// Dog automatically satisfies the Speaker interface.
//
// Go does NOT require:
// "implements Speaker"
//
// Interface implementation is implicit.
func (d Dog) Speak() string {
	return d.Name + " says Woof!"
}

// printSpeech accepts Speaker instead of Dog.
//
// Because of this, the function can accept ANY type
// that provides:
//
//	Speak() string
func printSpeech(s Speaker) {
	fmt.Println(s.Speak())
}

func main() {
	// Create a concrete Dog value.
	dog := Dog{
		Name: "Buddy",
	}

	// Dog satisfies Speaker,
	// so Dog can be passed here.
	printSpeech(dog)

	// Output:
	//
	// Buddy says Woof!
}

2. One Interface, Multiple Types

package main

import "fmt"

// Speaker describes one capability:
//
// "I can speak."
type Speaker interface {
	Speak() string
}

// Dog is one completely independent type.
type Dog struct {
	Name string
}

// Dog satisfies Speaker because Dog has Speak() string.
func (d Dog) Speak() string {
	return d.Name + ": Woof!"
}

// Person is another completely different type.
type Person struct {
	Name string
}

// Person also satisfies Speaker.
//
// Notice:
// Person and Dog do NOT inherit from each other.
//
// They simply provide the same required behavior.
func (p Person) Speak() string {
	return p.Name + ": Hello!"
}

// Robot is another unrelated type.
type Robot struct {
	ID string
}

// Robot also satisfies Speaker.
func (r Robot) Speak() string {
	return r.ID + ": Beep beep!"
}

// announce does not care whether the value is:
//
// - Dog
// - Person
// - Robot
//
// It only cares that the value can Speak().
func announce(s Speaker) {
	fmt.Println(s.Speak())
}

func main() {
	dog := Dog{Name: "Buddy"}
	person := Person{Name: "Alice"}
	robot := Robot{ID: "R2"}

	announce(dog)
	announce(person)
	announce(robot)

	// Output:
	//
	// Buddy: Woof!
	// Alice: Hello!
	// R2: Beep beep!
}

3. Interface with Multiple Methods

package main

import "fmt"

// Animal requires TWO methods.
//
// A type must provide BOTH methods
// to satisfy Animal.
type Animal interface {
	Speak() string
	Move() string
}

type Dog struct {
	Name string
}

// First required method.
func (d Dog) Speak() string {
	return d.Name + " says Woof!"
}

// Second required method.
func (d Dog) Move() string {
	return d.Name + " is running"
}

// describeAnimal can safely call both methods
// because Animal guarantees both exist.
func describeAnimal(a Animal) {
	fmt.Println(a.Speak())
	fmt.Println(a.Move())
}

func main() {
	dog := Dog{Name: "Buddy"}

	describeAnimal(dog)

	// Output:
	//
	// Buddy says Woof!
	// Buddy is running
}

4. Missing a Required Method

package main

// Animal requires both Speak and Move.
type Animal interface {
	Speak() string
	Move() string
}

type Cat struct{}

// Cat has Speak().
func (Cat) Speak() string {
	return "Meow"
}

// Cat does NOT have:
//
//	func (Cat) Move() string
//
// Therefore Cat does NOT satisfy Animal.

func main() {
	cat := Cat{}

	_ = cat

	// This would NOT compile:
	//
	// var animal Animal = cat
	//
	// Compiler reason:
	//
	// Cat does not implement Animal
	// because Cat is missing Move().
}

5. Interface Method Signatures Must Match Exactly

package main

// Speaker requires:
//
//	Speak() string
type Speaker interface {
	Speak() string
}

type Dog struct{}

// This method does NOT satisfy Speaker.
//
// Why?
//
// Speaker requires:
//
//	Speak() string
//
// But Dog provides:
//
//	Speak(int) string
//
// The parameter list is different.
func (Dog) Speak(volume int) string {
	if volume > 5 {
		return "WOOF!"
	}

	return "woof"
}

func main() {
	dog := Dog{}

	_ = dog

	// This would fail:
	//
	// var speaker Speaker = dog
	//
	// because:
	//
	// Speak(int) string
	//
	// is not the same method signature as:
	//
	// Speak() string
}

6. Storing a Concrete Value Inside an Interface

package main

import "fmt"

type Speaker interface {
	Speak() string
}

type Dog struct {
	Name string
}

func (d Dog) Speak() string {
	return "Woof from " + d.Name
}

func main() {
	// speaker has STATIC type Speaker.
	var speaker Speaker

	// We store a concrete Dog inside it.
	speaker = Dog{
		Name: "Buddy",
	}

	// Conceptually the interface now contains:
	//
	// dynamic type  = Dog
	// dynamic value = Dog{Name: "Buddy"}
	//
	// The variable itself is still typed as Speaker.

	fmt.Println(speaker.Speak())

	// Output:
	//
	// Woof from Buddy
}

7. Practical Use Case: Notification System

package main

import "fmt"

// Notifier represents one behavior:
//
// "I know how to send a notification."
//
// The business code does not need to know
// HOW the notification is sent.
type Notifier interface {
	Notify(message string) error
}

// EmailNotifier is one implementation.
type EmailNotifier struct {
	Email string
}

// Notify makes EmailNotifier satisfy Notifier.
func (e EmailNotifier) Notify(message string) error {
	fmt.Println("EMAIL TO:", e.Email)
	fmt.Println("MESSAGE:", message)

	return nil
}

// SMSNotifier is another implementation.
type SMSNotifier struct {
	Phone string
}

// Same interface method,
// completely different implementation.
func (s SMSNotifier) Notify(message string) error {
	fmt.Println("SMS TO:", s.Phone)
	fmt.Println("MESSAGE:", message)

	return nil
}

// sendAlert works with ANY Notifier.
//
// This is the important abstraction:
//
// sendAlert does not depend on EmailNotifier.
// sendAlert does not depend on SMSNotifier.
//
// It depends only on:
// "something that can Notify".
func sendAlert(n Notifier, message string) error {
	return n.Notify(message)
}

func main() {
	email := EmailNotifier{
		Email: "alice@example.com",
	}

	sms := SMSNotifier{
		Phone: "+123456789",
	}

	_ = sendAlert(email, "Server is down")
	_ = sendAlert(sms, "Server is down")
}

8. Practical Use Case: Payment Processing

package main

import "fmt"

// PaymentProcessor defines what checkout needs.
//
// Checkout does not care whether payment is handled by:
//
// - credit card
// - bank
// - wallet
// - fake test implementation
//
// It only needs Charge().
type PaymentProcessor interface {
	Charge(amount float64) error
}

type CreditCardProcessor struct{}

func (CreditCardProcessor) Charge(amount float64) error {
	fmt.Printf("Charging credit card: $%.2f\n", amount)

	return nil
}

type BankProcessor struct{}

func (BankProcessor) Charge(amount float64) error {
	fmt.Printf("Charging bank account: $%.2f\n", amount)

	return nil
}

// checkout depends on behavior,
// not a specific payment provider.
func checkout(
	processor PaymentProcessor,
	amount float64,
) error {
	return processor.Charge(amount)
}

func main() {
	card := CreditCardProcessor{}
	bank := BankProcessor{}

	_ = checkout(card, 100)
	_ = checkout(bank, 250)
}

9. Value Receiver and Interface

package main

import "fmt"

type Reader interface {
	Read() string
}

type Book struct {
	Title string
}

// Read uses a VALUE receiver:
//
//	(b Book)
//
// Therefore the method belongs to the method set
// needed for Book values.
//
// A *Book can also use value-receiver methods.
func (b Book) Read() string {
	return "Reading " + b.Title
}

func printReading(r Reader) {
	fmt.Println(r.Read())
}

func main() {
	// Concrete value.
	bookValue := Book{
		Title: "Learning Go",
	}

	// Pointer to concrete value.
	bookPointer := &Book{
		Title: "Advanced Go",
	}

	// Both work because Read uses a value receiver.
	printReading(bookValue)
	printReading(bookPointer)
}

10. Pointer Receiver and Interface

package main

import "fmt"

type Saver interface {
	Save()
}

type Document struct {
	Name string
}

// Save uses a POINTER receiver:
//
//	(d *Document)
//
// Therefore *Document satisfies Saver.
//
// Plain Document does NOT have this pointer-receiver
// method in its method set for interface satisfaction.
func (d *Document) Save() {
	fmt.Println("Saving:", d.Name)
}

func main() {
	// Pointer value.
	document := &Document{
		Name: "report.txt",
	}

	// This works because *Document satisfies Saver.
	var saver Saver = document

	saver.Save()

	// This would NOT compile:
	//
	// var saver2 Saver = Document{
	//     Name: "notes.txt",
	// }
	//
	// Reason:
	//
	// Save() is defined on *Document,
	// not on Document.
}

11. Direct Method Calls vs Interface Method Sets

package main

import "fmt"

type Saver interface {
	Save()
}

type File struct {
	Name string
}

// Pointer receiver.
func (f *File) Save() {
	fmt.Println("Saving:", f.Name)
}

func main() {
	// file is a normal value.
	file := File{
		Name: "data.txt",
	}

	// This works.
	//
	// Because file is addressable,
	// Go can automatically treat this approximately like:
	//
	// (&file).Save()
	file.Save()

	// But this is a DIFFERENT question:
	//
	// Does File satisfy Saver?
	//
	// No.
	//
	// *File satisfies Saver.
	//
	// Therefore this works:
	var saver Saver = &file

	saver.Save()

	// But this would fail:
	//
	// var wrong Saver = file
}

12. Method-Set Rule

package main

// Reader requires Read().
type Reader interface {
	Read()
}

// Writer requires Write().
type Writer interface {
	Write()
}

type User struct{}

// Value receiver.
//
// Conceptually:
//
// User has Read()
// *User also has Read()
func (User) Read() {}

// Pointer receiver.
//
// Conceptually:
//
// *User has Write()
//
// Plain User does not satisfy an interface
// requiring Write().
func (*User) Write() {}

func main() {
	user := User{}

	// User satisfies Reader.
	var r1 Reader = user

	// *User also satisfies Reader.
	var r2 Reader = &user

	// *User satisfies Writer.
	var w1 Writer = &user

	_, _, _ = r1, r2, w1

	// This would fail:
	//
	// var w2 Writer = user
}

13. Compile-Time Interface Check

package main

type Notifier interface {
	Notify(string) error
}

type EmailNotifier struct{}

func (*EmailNotifier) Notify(message string) error {
	return nil
}

// This line performs a compile-time interface check.
//
// It asks:
//
// "Does *EmailNotifier satisfy Notifier?"
//
// The blank identifier _ means:
//
// "We don't need to store this value."
//
// If EmailNotifier stops satisfying Notifier,
// compilation fails immediately.
var _ Notifier = (*EmailNotifier)(nil)

func main() {}

14. The Empty Interface

package main

import "fmt"

func main() {
	// interface{} contains ZERO required methods.
	//
	// Every normal Go type satisfies an interface
	// with zero method requirements.
	var value interface{}

	value = 100
	fmt.Println(value)

	value = "Hello"
	fmt.Println(value)

	value = true
	fmt.Println(value)

	value = []int{1, 2, 3}
	fmt.Println(value)

	// Output:
	//
	// 100
	// Hello
	// true
	// [1 2 3]
}

15. any

package main

import "fmt"

// any is an alias for interface{}.
//
// These are equivalent:
//
//	var x interface{}
//
//	var x any
//
// In modern Go code, any is usually easier to read
// when arbitrary values are genuinely needed.
func printAnything(value any) {
	fmt.Println(value)
}

func main() {
	printAnything(10)
	printAnything("Go")
	printAnything(true)
	printAnything(3.14)
	printAnything([]string{"A", "B"})
}

16. Do Not Use any When a Better Type Exists

package main

// This function is clear.
//
// The compiler knows exactly what goes in
// and exactly what comes out.
func addGood(a, b int) int {
	return a + b
}

// This design would be much harder to understand:
//
//	func addBad(a, b any) any
//
// Problems:
//
// 1. What types are allowed?
// 2. What type is returned?
// 3. What happens for strings?
// 4. What happens for structs?
// 5. Errors move from compile time toward runtime.
//
// Use any only when arbitrary types are actually part
// of the problem you are solving.

func main() {
	result := addGood(10, 20)

	_ = result
}

17. Type Assertion

package main

import "fmt"

func main() {
	// value has static type any.
	//
	// Its dynamic type is string.
	var value any = "Hello Go"

	// Type assertion syntax:
	//
	//	interfaceValue.(ConcreteType)
	//
	// Here we are saying:
	//
	// "Give me the string stored inside value."
	text := value.(string)

	fmt.Println(text)

	// Output:
	//
	// Hello Go
}

18. Unsafe Type Assertion

package main

func main() {
	var value any = 100

	// value contains an int.
	//
	// Therefore this assertion would panic:
	//
	// text := value.(string)
	//
	// Runtime reason:
	//
	// value contains int,
	// but we demanded string.

	_ = value
}

19. Safe Type Assertion

package main

import "fmt"

func main() {
	var value any = "Hello"

	// Two-value type assertion:
	//
	//	text, ok := value.(string)
	//
	// text receives the value if successful.
	//
	// ok becomes true when successful.
	//
	// This avoids a panic.
	text, ok := value.(string)

	if !ok {
		fmt.Println("value is not a string")
		return
	}

	fmt.Println("String:", text)

	// Output:
	//
	// String: Hello
}

20. Failed Safe Type Assertion

package main

import "fmt"

func main() {
	var value any = 123

	text, ok := value.(string)

	// Assertion fails.
	//
	// ok becomes false.
	//
	// text receives string's zero value:
	//
	// ""
	fmt.Printf("text = %q\n", text)
	fmt.Println("ok =", ok)

	// Output:
	//
	// text = ""
	// ok = false
}

21. Type Switch

package main

import "fmt"

func describe(value any) {
	// value.(type) is special syntax
	// used inside a type switch.
	switch v := value.(type) {

	case int:
		// Inside this case,
		// v is an int.
		fmt.Println("Integer:", v)

	case string:
		// Here v is a string.
		fmt.Println("String:", v)

	case bool:
		// Here v is a bool.
		fmt.Println("Boolean:", v)

	case float64:
		// Here v is a float64.
		fmt.Println("Float:", v)

	default:
		// Any unmatched type arrives here.
		fmt.Println("Unknown type")
	}
}

func main() {
	describe(10)
	describe("Go")
	describe(true)
	describe(3.14)
	describe([]int{1, 2})

	// Output:
	//
	// Integer: 10
	// String: Go
	// Boolean: true
	// Float: 3.14
	// Unknown type
}

22. Interface Composition

package main

import "fmt"

// Reader describes reading behavior.
type Reader interface {
	Read() string
}

// Writer describes writing behavior.
type Writer interface {
	Write(string)
}

// ReadWriter embeds both interfaces.
//
// Therefore a value must provide:
//
//	Read() string
//	Write(string)
//
// to satisfy ReadWriter.
type ReadWriter interface {
	Reader
	Writer
}

type Document struct {
	Content string
}

func (d Document) Read() string {
	return d.Content
}

func (d *Document) Write(content string) {
	d.Content = content
}

func useDocument(rw ReadWriter) {
	rw.Write("Go Interfaces")

	fmt.Println(rw.Read())
}

func main() {
	// We use *Document because Write has a pointer receiver.
	document := &Document{}

	useDocument(document)

	// Output:
	//
	// Go Interfaces
}

23. Small Interfaces

package main

// Small interfaces usually create lower coupling.

// Reader needs only reading behavior.
type Reader interface {
	Read() string
}

// Writer needs only writing behavior.
type Writer interface {
	Write(string)
}

// Deleter needs only deletion behavior.
type Deleter interface {
	Delete() error
}

// Instead of forcing every consumer to depend on:
//
// type Everything interface {
//     Read() string
//     Write(string)
//     Delete() error
//     Export() error
//     Backup() error
//     Email() error
// }
//
// each consumer can depend only on what it actually needs.

func display(r Reader) {
	// display only needs Read().
	//
	// Therefore requiring Writer or Deleter here
	// would unnecessarily increase coupling.
	_ = r.Read()
}

func main() {}

24. Consumer-Side Interface

package main

type User struct {
	ID   int
	Name string
}

// Imagine a real database repository has many methods:
//
// Save
// Delete
// FindByID
// FindByEmail
// List
// Update
// Count
//
// But this service needs only FindByID.
//
// Therefore the consumer can define a tiny interface
// containing only what it actually uses.
type UserFinder interface {
	FindByID(id int) (User, error)
}

type UserService struct {
	finder UserFinder
}

func NewUserService(finder UserFinder) *UserService {
	return &UserService{
		finder: finder,
	}
}

func (s *UserService) GetUser(id int) (User, error) {
	return s.finder.FindByID(id)
}

func main() {}

25. Dependency Injection with Interfaces

package main

import "fmt"

// EmailSender describes the dependency
// needed by UserService.
type EmailSender interface {
	Send(to string, message string) error
}

// UserService stores the INTERFACE,
// not a specific SMTP implementation.
type UserService struct {
	emailSender EmailSender
}

// The dependency is supplied from outside.
//
// This is dependency injection.
//
// No special framework is required.
func NewUserService(
	sender EmailSender,
) *UserService {
	return &UserService{
		emailSender: sender,
	}
}

func (s *UserService) Register(email string) error {
	fmt.Println("Creating user:", email)

	// UserService does not know HOW email is sent.
	//
	// It only knows the dependency supports Send().
	return s.emailSender.Send(
		email,
		"Welcome!",
	)
}

// Production implementation.
type SMTPEmailSender struct{}

func (SMTPEmailSender) Send(
	to string,
	message string,
) error {
	fmt.Println("SMTP email to:", to)
	fmt.Println("Message:", message)

	return nil
}

func main() {
	sender := SMTPEmailSender{}

	service := NewUserService(sender)

	_ = service.Register("alice@example.com")
}

26. Testing with a Fake Implementation

package main

import "fmt"

type EmailSender interface {
	Send(to string, message string) error
}

type UserService struct {
	sender EmailSender
}

func NewUserService(sender EmailSender) *UserService {
	return &UserService{
		sender: sender,
	}
}

func (s *UserService) Register(email string) error {
	return s.sender.Send(
		email,
		"Welcome!",
	)
}

// FakeEmailSender is used during tests.
//
// Instead of sending a real email,
// it records what happened.
type FakeEmailSender struct {
	Called  bool
	To      string
	Message string
}

func (f *FakeEmailSender) Send(
	to string,
	message string,
) error {
	f.Called = true
	f.To = to
	f.Message = message

	return nil
}

func main() {
	// Create fake dependency.
	fake := &FakeEmailSender{}

	// Inject fake dependency.
	service := NewUserService(fake)

	_ = service.Register("alice@example.com")

	// Tests can inspect what happened.
	fmt.Println(fake.Called)
	fmt.Println(fake.To)
	fmt.Println(fake.Message)

	// Output:
	//
	// true
	// alice@example.com
	// Welcome!
}

27. Repository Interface

package main

import (
	"errors"
	"fmt"
)

type User struct {
	ID   int
	Name string
}

// Business logic depends on this behavior,
// not on PostgreSQL/MySQL/etc.
type UserRepository interface {
	Save(user User) error
	FindByID(id int) (User, error)
}

// MemoryUserRepository is one implementation.
//
// This could be used for:
//
// - learning
// - tests
// - prototypes
type MemoryUserRepository struct {
	users map[int]User
}

func NewMemoryUserRepository() *MemoryUserRepository {
	return &MemoryUserRepository{
		users: make(map[int]User),
	}
}

func (r *MemoryUserRepository) Save(user User) error {
	r.users[user.ID] = user

	return nil
}

func (r *MemoryUserRepository) FindByID(
	id int,
) (User, error) {
	user, ok := r.users[id]

	if !ok {
		return User{}, errors.New("user not found")
	}

	return user, nil
}

func main() {
	var repository UserRepository

	repository = NewMemoryUserRepository()

	_ = repository.Save(User{
		ID:   1,
		Name: "Alice",
	})

	user, err := repository.FindByID(1)

	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(user.Name)

	// Output:
	//
	// Alice
}

28. The error Interface

package main

import "fmt"

// error is itself an interface.
//
// Conceptually:
//
// type error interface {
//     Error() string
// }
//
// Therefore any type that provides:
//
//	Error() string
//
// can be returned as an error.

type ValidationError struct {
	Field   string
	Message string
}

// By implementing Error() string,
// ValidationError satisfies the built-in error interface.
func (e ValidationError) Error() string {
	return e.Field + ": " + e.Message
}

func validateAge(age int) error {
	if age < 18 {
		// ValidationError can be returned here
		// because it satisfies error.
		return ValidationError{
			Field:   "age",
			Message: "must be 18 or older",
		}
	}

	return nil
}

func main() {
	err := validateAge(15)

	if err != nil {
		fmt.Println(err)

		// Output:
		//
		// age: must be 18 or older
	}
}

29. Standard Interface Example: fmt.Stringer

package main

import "fmt"

type User struct {
	ID   int
	Name string
}

// fmt.Stringer is conceptually:
//
// type Stringer interface {
//     String() string
// }
//
// By implementing String(),
// User can control how fmt prints it.
func (u User) String() string {
	return fmt.Sprintf(
		"User(ID=%d, Name=%s)",
		u.ID,
		u.Name,
	)
}

func main() {
	user := User{
		ID:   1,
		Name: "Alice",
	}

	fmt.Println(user)

	// Because User satisfies fmt.Stringer,
	// fmt uses user.String().
	//
	// Output:
	//
	// User(ID=1, Name=Alice)
}

30. Standard Interface Example: io.Reader

package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	// strings.NewReader returns a value
	// that satisfies io.Reader.
	reader := strings.NewReader(
		"Hello Go Interfaces",
	)

	// io.ReadAll does not care that the concrete
	// implementation came from strings.NewReader.
	//
	// It accepts io.Reader behavior.
	data, err := io.ReadAll(reader)

	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(string(data))

	// Output:
	//
	// Hello Go Interfaces
}

31. Why Standard Interfaces Are Powerful

package main

import (
	"bytes"
	"io"
	"strings"
)

// processData accepts io.Reader.
//
// Therefore it can work with many implementations:
//
// - files
// - HTTP response bodies
// - strings.Reader
// - bytes.Buffer
// - compressed streams
// - network streams
//
// The function needs reading behavior,
// not one particular concrete type.
func processData(r io.Reader) error {
	_, err := io.ReadAll(r)

	return err
}

func main() {
	// strings.Reader satisfies io.Reader.
	stringReader := strings.NewReader("hello")

	_ = processData(stringReader)

	// bytes.Buffer also satisfies io.Reader.
	buffer := bytes.NewBufferString("hello")

	_ = processData(buffer)
}

32. Function Adapter Pattern

package main

import "fmt"

// Handler requires a method.
type Handler interface {
	Handle(message string) error
}

// HandlerFunc is a NAMED function type.
type HandlerFunc func(string) error

// By giving HandlerFunc a Handle method,
// HandlerFunc satisfies Handler.
func (f HandlerFunc) Handle(
	message string,
) error {
	// Call the original function.
	return f(message)
}

// This is a normal standalone function.
func printMessage(message string) error {
	fmt.Println(message)

	return nil
}

func execute(h Handler) error {
	return h.Handle("Hello")
}

func main() {
	// Convert the normal function
	// into HandlerFunc.
	handler := HandlerFunc(printMessage)

	// HandlerFunc satisfies Handler.
	_ = execute(handler)

	// Output:
	//
	// Hello
}

33. Optional Capability with Type Assertion

package main

import "fmt"

type Writer interface {
	Write(string)
}

type Flusher interface {
	Flush()
}

type Buffer struct{}

func (Buffer) Write(data string) {
	fmt.Println("Writing:", data)
}

func (Buffer) Flush() {
	fmt.Println("Flushing")
}

func process(w Writer) {
	w.Write("Hello")

	// w is guaranteed only to be a Writer.
	//
	// But the concrete value MAY also support Flusher.
	//
	// This safe interface assertion checks
	// for that optional capability.
	if flusher, ok := w.(Flusher); ok {
		flusher.Flush()
	}
}

func main() {
	buffer := Buffer{}

	process(buffer)

	// Output:
	//
	// Writing: Hello
	// Flushing
}

34. Nil Interface

package main

import "fmt"

type Speaker interface {
	Speak()
}

func main() {
	// No concrete type.
	// No concrete value.
	var speaker Speaker

	// Conceptually:
	//
	// dynamic type  = nil
	// dynamic value = nil
	//
	// Therefore the interface itself is nil.
	fmt.Println(speaker == nil)

	// Output:
	//
	// true
}

35. Typed Nil Inside an Interface

package main

import "fmt"

type Speaker interface {
	Speak()
}

type Dog struct{}

func (*Dog) Speak() {}

func main() {
	// dog is a nil pointer.
	var dog *Dog = nil

	// Store that typed nil pointer
	// inside an interface.
	var speaker Speaker = dog

	// Conceptually speaker contains:
	//
	// dynamic type  = *Dog
	// dynamic value = nil
	//
	// Because the interface contains type information,
	// the interface itself is NOT nil.
	fmt.Println(speaker == nil)

	// Output:
	//
	// false
}

36. Typed Nil Error Trap

package main

import "fmt"

type MyError struct {
	Message string
}

// Pointer receiver means *MyError satisfies error.
func (e *MyError) Error() string {
	if e == nil {
		return "nil MyError"
	}

	return e.Message
}

func badFunction() error {
	// err is a nil *MyError pointer.
	var err *MyError

	// Returning err puts this inside error:
	//
	// dynamic type  = *MyError
	// dynamic value = nil
	//
	// Therefore the returned error interface
	// is not nil.
	return err
}

func goodFunction() error {
	// When there is no error,
	// return a true nil interface.
	return nil
}

func main() {
	err1 := badFunction()
	err2 := goodFunction()

	fmt.Println(err1 == nil)
	fmt.Println(err2 == nil)

	// Output:
	//
	// false
	// true
}

37. Interface Adapter for an External API

package main

import "fmt"

// Our application wants this interface.
type Notifier interface {
	Notify(message string) error
}

// Imagine this type comes from another package
// and we cannot modify it.
type ExternalClient struct{}

// Its method name does not match our interface.
func (ExternalClient) SendMessage(
	message string,
) error {
	fmt.Println("External API:", message)

	return nil
}

// Adapter wraps the external client.
type NotificationAdapter struct {
	client ExternalClient
}

// Adapter translates our application's
// Notify() call into the external API's
// SendMessage() call.
func (a NotificationAdapter) Notify(
	message string,
) error {
	return a.client.SendMessage(message)
}

func sendNotification(n Notifier) error {
	return n.Notify("Order completed")
}

func main() {
	client := ExternalClient{}

	adapter := NotificationAdapter{
		client: client,
	}

	_ = sendNotification(adapter)
}

38. Decorator-Style Interface Composition

package main

import "fmt"

type User struct {
	Name string
}

type UserRepository interface {
	Save(User) error
}

// Real repository.
type MemoryRepository struct{}

func (MemoryRepository) Save(user User) error {
	fmt.Println("Saving:", user.Name)

	return nil
}

// LoggingRepository wraps another repository.
type LoggingRepository struct {
	next UserRepository
}

// Save adds behavior BEFORE and AFTER
// delegating to the wrapped repository.
func (r LoggingRepository) Save(
	user User,
) error {
	fmt.Println("LOG: Save started")

	err := r.next.Save(user)

	fmt.Println("LOG: Save finished")

	return err
}

func main() {
	realRepository := MemoryRepository{}

	loggedRepository := LoggingRepository{
		next: realRepository,
	}

	_ = loggedRepository.Save(
		User{Name: "Alice"},
	)

	// Output:
	//
	// LOG: Save started
	// Saving: Alice
	// LOG: Save finished
}

39. Interface Segregation

package main

// Bad design for many consumers:
//
// type HugeRepository interface {
//     FindByID(int) (User, error)
//     Save(User) error
//     Delete(int) error
//     Export() error
//     Backup() error
//     Restore() error
// }
//
// A consumer that only reads a user
// should not need all those methods.

type User struct {
	ID int
}

// Small interface for reading.
type UserFinder interface {
	FindByID(int) (User, error)
}

// Small interface for saving.
type UserSaver interface {
	Save(User) error
}

// Small interface for deleting.
type UserDeleter interface {
	Delete(int) error
}

// A larger interface can still be composed
// when a consumer truly needs everything.
type UserRepository interface {
	UserFinder
	UserSaver
	UserDeleter
}

func main() {}

40. Do Not Create Interfaces Too Early

package main

// This concrete type may be perfectly sufficient
// when there is:
//
// - one implementation
// - no testing boundary
// - no substitution requirement
// - no meaningful abstraction yet
type Calculator struct{}

func (Calculator) Add(a, b int) int {
	return a + b
}

// There is no need to automatically create:
//
// type CalculatorInterface interface {
//     Add(a, b int) int
// }
//
// simply because Calculator exists.
//
// Introduce interfaces when they represent
// a useful behavioral boundary.

func main() {
	calculator := Calculator{}

	result := calculator.Add(10, 20)

	_ = result
}

41. Accept Interface, Return Concrete Type

package main

type Logger interface {
	Log(string)
}

type Service struct {
	logger Logger
}

// The constructor ACCEPTS an interface.
//
// This allows callers to provide:
//
// - console logger
// - file logger
// - cloud logger
// - fake logger
func NewService(logger Logger) *Service {
	// The constructor RETURNS *Service,
	// a useful concrete type.
	return &Service{
		logger: logger,
	}
}

func (s *Service) Run() {
	s.logger.Log("service started")
}

func main() {}

42. Generic Constraint Interface

package main

import "fmt"

// Interfaces are also used as constraints
// for generic type parameters.
//
// This interface describes a TYPE SET.
//
// T may have an underlying type matching:
//
// int
// int64
// float64
type Number interface {
	~int | ~int64 | ~float64
}

// T must satisfy Number.
func Add[T Number](a, b T) T {
	// Addition is allowed because every type
	// permitted by Number supports +.
	return a + b
}

func main() {
	fmt.Println(Add(10, 20))
	fmt.Println(Add(1.5, 2.5))

	// Output:
	//
	// 30
	// 4
}

43. Why ~ Matters in Generic Interfaces

package main

import "fmt"

// UserID is a custom defined type.
//
// Its underlying type is int.
type UserID int

// ~int means:
//
// "int OR any defined type whose underlying type is int."
type Integer interface {
	~int
}

func Double[T Integer](value T) T {
	return value * 2
}

func main() {
	var id UserID = 10

	// UserID satisfies ~int
	// because its underlying type is int.
	result := Double(id)

	fmt.Println(result)

	// Output:
	//
	// 20
}

44. comparable Interface Constraint

package main

import "fmt"

// comparable is a built-in constraint.
//
// T must support:
//
// ==
// !=
func Equal[T comparable](a, b T) bool {
	return a == b
}

func main() {
	fmt.Println(Equal(10, 10))
	fmt.Println(Equal(10, 20))

	fmt.Println(Equal("Go", "Go"))
	fmt.Println(Equal("Go", "Java"))

	// Output:
	//
	// true
	// false
	// true
	// false
}

45. Generic Set Using comparable

package main

import "fmt"

// Map keys must be comparable.
//
// Therefore T is constrained by comparable.
type Set[T comparable] map[T]struct{}

func (s Set[T]) Add(value T) {
	// Empty struct{} takes no meaningful payload space.
	s[value] = struct{}{}
}

func (s Set[T]) Contains(value T) bool {
	_, ok := s[value]

	return ok
}

func main() {
	numbers := Set[int]{}

	numbers.Add(10)
	numbers.Add(20)

	fmt.Println(numbers.Contains(10))
	fmt.Println(numbers.Contains(99))

	// Output:
	//
	// true
	// false
}

46. Runtime Interface vs Generic Constraint

package main

// Runtime behavioral interface.
//
// Values can be stored in variables of this interface type.
type Speaker interface {
	Speak() string
}

// Generic constraint interface.
//
// Its type terms describe what compile-time
// generic types are permitted.
type Number interface {
	~int | ~float64
}

func PrintSpeaker(s Speaker) {
	// Runtime polymorphism:
	//
	// the concrete type can vary at runtime.
	_ = s.Speak()
}

func Add[T Number](a, b T) T {
	// Compile-time generic reuse:
	//
	// the compiler works with the selected T.
	return a + b
}

func main() {}

47. Interfaces vs Generics

package main

// Use an interface when the important question is:
//
// "What behavior does this value provide?"

type Saver interface {
	Save() error
}

func persist(s Saver) error {
	return s.Save()
}

// Use generics when the important question is:
//
// "Can this algorithm work with many compile-time types?"

func First[T any](values []T) T {
	return values[0]
}

// Interfaces and generics are not competitors.
//
// Interfaces:
// - behavior abstraction
// - runtime polymorphism
// - dependency boundaries
//
// Generics:
// - reusable algorithms
// - reusable containers
// - compile-time type relationships

func main() {}

48. Real-World Use Case: Clock Interface for Testing

package main

import (
	"fmt"
	"time"
)

// Clock describes the only time behavior
// our service needs.
type Clock interface {
	Now() time.Time
}

// RealClock is used in production.
type RealClock struct{}

func (RealClock) Now() time.Time {
	return time.Now()
}

// FakeClock is useful in tests.
type FakeClock struct {
	Current time.Time
}

func (f FakeClock) Now() time.Time {
	return f.Current
}

type GreetingService struct {
	clock Clock
}

func (s GreetingService) Greeting() string {
	hour := s.clock.Now().Hour()

	if hour < 12 {
		return "Good morning"
	}

	return "Good afternoon"
}

func main() {
	// Test code can control time exactly.
	fake := FakeClock{
		Current: time.Date(
			2026,
			time.August,
			17,
			9,
			0,
			0,
			0,
			time.UTC,
		),
	}

	service := GreetingService{
		clock: fake,
	}

	fmt.Println(service.Greeting())

	// Output:
	//
	// Good morning
}

49. Real-World Use Case: ID Generator

package main

import "fmt"

// IDGenerator abstracts ID creation.
type IDGenerator interface {
	Generate() string
}

// FixedIDGenerator is extremely useful in tests.
type FixedIDGenerator struct {
	ID string
}

func (g FixedIDGenerator) Generate() string {
	return g.ID
}

type User struct {
	ID   string
	Name string
}

type UserService struct {
	generator IDGenerator
}

func (s UserService) Create(name string) User {
	return User{
		// Service does not know how IDs are generated.
		ID: s.generator.Generate(),

		Name: name,
	}
}

func main() {
	generator := FixedIDGenerator{
		ID: "test-123",
	}

	service := UserService{
		generator: generator,
	}

	user := service.Create("Alice")

	fmt.Println(user.ID)
	fmt.Println(user.Name)

	// Output:
	//
	// test-123
	// Alice
}

50. Real-World Use Case: Cache

package main

import "fmt"

// Cache describes the behavior
// required by the service.
//
// Production could use Redis.
//
// Tests could use MemoryCache.
type Cache interface {
	Get(key string) (string, bool)
	Set(key string, value string)
}

type MemoryCache struct {
	values map[string]string
}

func NewMemoryCache() *MemoryCache {
	return &MemoryCache{
		values: make(map[string]string),
	}
}

func (c *MemoryCache) Get(
	key string,
) (string, bool) {
	value, ok := c.values[key]

	return value, ok
}

func (c *MemoryCache) Set(
	key string,
	value string,
) {
	c.values[key] = value
}

func main() {
	var cache Cache = NewMemoryCache()

	cache.Set("language", "Go")

	value, found := cache.Get("language")

	fmt.Println(value)
	fmt.Println(found)

	// Output:
	//
	// Go
	// true
}

51. Complete Mini Project: Order Processing

package main

import "fmt"

// Order is normal application data.
type Order struct {
	ID     int
	Email  string
	Amount float64
}

// OrderRepository describes persistence behavior.
type OrderRepository interface {
	Save(order Order) error
}

// PaymentProcessor describes payment behavior.
type PaymentProcessor interface {
	Charge(amount float64) error
}

// Notifier describes notification behavior.
type Notifier interface {
	Notify(
		recipient string,
		message string,
	) error
}

// OrderService depends only on interfaces.
//
// It does NOT directly depend on:
//
// PostgreSQL
// Stripe
// SMTP
//
// This keeps business logic separate
// from infrastructure details.
type OrderService struct {
	repository OrderRepository
	payment    PaymentProcessor
	notifier   Notifier
}

// Constructor injects dependencies.
func NewOrderService(
	repository OrderRepository,
	payment PaymentProcessor,
	notifier Notifier,
) *OrderService {
	return &OrderService{
		repository: repository,
		payment:    payment,
		notifier:   notifier,
	}
}

// Process contains business workflow.
func (s *OrderService) Process(
	order Order,
) error {
	// First charge payment.
	if err := s.payment.Charge(
		order.Amount,
	); err != nil {
		return err
	}

	// Then save the order.
	if err := s.repository.Save(
		order,
	); err != nil {
		return err
	}

	// Then notify the customer.
	if err := s.notifier.Notify(
		order.Email,
		"Your order was processed",
	); err != nil {
		return err
	}

	return nil
}

// ------------------------------
// Repository implementation
// ------------------------------

type MemoryOrderRepository struct {
	orders []Order
}

func (r *MemoryOrderRepository) Save(
	order Order,
) error {
	r.orders = append(
		r.orders,
		order,
	)

	fmt.Println(
		"Saved order:",
		order.ID,
	)

	return nil
}

// ------------------------------
// Payment implementation
// ------------------------------

type FakePaymentProcessor struct{}

func (FakePaymentProcessor) Charge(
	amount float64,
) error {
	fmt.Printf(
		"Charged: $%.2f\n",
		amount,
	)

	return nil
}

// ------------------------------
// Notification implementation
// ------------------------------

type ConsoleNotifier struct{}

func (ConsoleNotifier) Notify(
	recipient string,
	message string,
) error {
	fmt.Println(
		"Notification to:",
		recipient,
	)

	fmt.Println(
		"Message:",
		message,
	)

	return nil
}

func main() {
	// Create concrete implementations.
	repository := &MemoryOrderRepository{}
	payment := FakePaymentProcessor{}
	notifier := ConsoleNotifier{}

	// Inject them through interfaces.
	service := NewOrderService(
		repository,
		payment,
		notifier,
	)

	order := Order{
		ID:     1001,
		Email:  "alice@example.com",
		Amount: 249.99,
	}

	if err := service.Process(order); err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Output:
	//
	// Charged: $249.99
	// Saved order: 1001
	// Notification to: alice@example.com
	// Message: Your order was processed
}

52. Testing the Order Service with Fakes

package main

import "fmt"

type Order struct {
	ID     int
	Email  string
	Amount float64
}

type OrderRepository interface {
	Save(Order) error
}

type PaymentProcessor interface {
	Charge(float64) error
}

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

type OrderService struct {
	repository OrderRepository
	payment    PaymentProcessor
	notifier   Notifier
}

func (s *OrderService) Process(
	order Order,
) error {
	if err := s.payment.Charge(
		order.Amount,
	); err != nil {
		return err
	}

	if err := s.repository.Save(
		order,
	); err != nil {
		return err
	}

	return s.notifier.Notify(
		order.Email,
		"processed",
	)
}

// Fake repository records what was saved.
type FakeRepository struct {
	SavedOrder Order
	Called     bool
}

func (f *FakeRepository) Save(
	order Order,
) error {
	f.Called = true
	f.SavedOrder = order

	return nil
}

// Fake payment records the charged amount.
type FakePayment struct {
	Amount float64
	Called bool
}

func (f *FakePayment) Charge(
	amount float64,
) error {
	f.Called = true
	f.Amount = amount

	return nil
}

// Fake notifier records notification details.
type FakeNotifier struct {
	Recipient string
	Message   string
	Called    bool
}

func (f *FakeNotifier) Notify(
	recipient string,
	message string,
) error {
	f.Called = true
	f.Recipient = recipient
	f.Message = message

	return nil
}

func main() {
	repository := &FakeRepository{}
	payment := &FakePayment{}
	notifier := &FakeNotifier{}

	service := OrderService{
		repository: repository,
		payment:    payment,
		notifier:   notifier,
	}

	order := Order{
		ID:     1,
		Email:  "alice@example.com",
		Amount: 100,
	}

	_ = service.Process(order)

	// A real unit test would use testing.T,
	// but these prints show what can be verified.

	fmt.Println(payment.Called)
	fmt.Println(payment.Amount)

	fmt.Println(repository.Called)
	fmt.Println(repository.SavedOrder.ID)

	fmt.Println(notifier.Called)
	fmt.Println(notifier.Recipient)

	// Output:
	//
	// true
	// 100
	// true
	// 1
	// true
	// alice@example.com
}

53. Common Mistake: Giant Interface

package main

// Avoid creating an interface like this
// unless a consumer truly requires ALL behavior.
//
// type ApplicationManager interface {
//     Create()
//     Read()
//     Update()
//     Delete()
//     Import()
//     Export()
//     Backup()
//     Restore()
//     SendEmail()
//     GenerateReport()
// }

// Prefer small interfaces.

type Creator interface {
	Create() error
}

type Reader interface {
	Read() error
}

type Updater interface {
	Update() error
}

type Deleter interface {
	Delete() error
}

// Compose them only where necessary.
type CRUDService interface {
	Creator
	Reader
	Updater
	Deleter
}

func main() {}

54. Common Mistake: Interface for Every Struct

package main

type User struct {
	Name string
}

// This method alone does NOT mean you need:
//
// type UserInterface interface {
//     NameValue() string
// }
//
// If callers can simply use User,
// use the concrete type.
//
// Interfaces should represent useful abstraction,
// not automatic wrappers around every struct.

func (u User) NameValue() string {
	return u.Name
}

func main() {
	user := User{
		Name: "Alice",
	}

	_ = user.NameValue()
}

55. Common Mistake: Overusing Type Switches

package main

// This kind of design:
//
// switch value := thing.(type) {
// case Dog:
// case Cat:
// case Person:
// case Robot:
// case Car:
// }
//
// may sometimes be necessary.
//
// But if every type performs the same conceptual
// behavior, an interface may be cleaner.

type Speaker interface {
	Speak() string
}

// Now callers can simply:
//
//	speaker.Speak()
//
// instead of repeatedly checking concrete types.

func announce(s Speaker) string {
	return s.Speak()
}

func main() {}

56. Common Mistake: Returning any Everywhere

package main

type User struct {
	ID   int
	Name string
}

// Poorly typed API:
//
// func FindUser(id int) any
//
// Caller would need to guess/assert the result type.

// Better API:
//
// The return type clearly tells the caller
// what to expect.
func FindUser(id int) (User, error) {
	return User{
		ID:   id,
		Name: "Alice",
	}, nil
}

func main() {}

57. Common Mistake: Wrong Receiver Type

package main

type Counter interface {
	Increment()
}

type NumberCounter struct {
	Value int
}

// Pointer receiver is required here
// because Increment must change Value.
func (c *NumberCounter) Increment() {
	c.Value++
}

func main() {
	counter := NumberCounter{}

	// Direct call works because Go can take
	// the address of an addressable variable.
	counter.Increment()

	// Interface assignment requires *NumberCounter.
	var incrementer Counter = &counter

	incrementer.Increment()

	// This would fail:
	//
	// var wrong Counter = counter
}

58. Interface Design Decision Example

package main

type User struct {
	ID int
}

// Question:
//
// "Should I create an interface here?"
//
// Start by asking:
//
// "What does the CONSUMER actually need?"

type UserFinder interface {
	FindByID(int) (User, error)
}

// This interface is useful when:
//
// 1. Multiple implementations may exist.
// 2. Tests need a fake implementation.
// 3. The dependency crosses an architectural boundary.
// 4. The consumer needs only this behavior.
//
// An interface is probably unnecessary when:
//
// 1. There is only a simple local concrete type.
// 2. No substitution is needed.
// 3. No testing boundary exists.
// 4. The abstraction adds more complexity than value.

func loadUser(
	finder UserFinder,
	id int,
) (User, error) {
	return finder.FindByID(id)
}

func main() {}

59. Final Interface Cheat Sheet

package main

// ------------------------------------
// BASIC INTERFACE
// ------------------------------------

type Speaker interface {
	Speak() string
}

// ------------------------------------
// IMPLEMENTATION
// ------------------------------------

type Dog struct{}

func (Dog) Speak() string {
	return "Woof"
}

// Dog now implicitly satisfies Speaker.

// ------------------------------------
// MULTIPLE METHODS
// ------------------------------------

type Service interface {
	Start() error
	Stop() error
}

// ------------------------------------
// INTERFACE COMPOSITION
// ------------------------------------

type Reader interface {
	Read()
}

type Writer interface {
	Write()
}

type ReadWriter interface {
	Reader
	Writer
}

// ------------------------------------
// ANY
// ------------------------------------

// any is equivalent to interface{}.
//
// Use it when arbitrary values are genuinely needed.

func printAnything(value any) {
	_ = value
}

// ------------------------------------
// TYPE ASSERTION
// ------------------------------------

func assertion(value any) {
	text, ok := value.(string)

	if ok {
		_ = text
	}
}

// ------------------------------------
// TYPE SWITCH
// ------------------------------------

func typeSwitch(value any) {
	switch v := value.(type) {
	case string:
		_ = v

	case int:
		_ = v
	}
}

// ------------------------------------
// COMPILE-TIME CHECK
// ------------------------------------

var _ Speaker = Dog{}

// ------------------------------------
// GENERIC CONSTRAINT
// ------------------------------------

type Number interface {
	~int | ~float64
}

func Add[T Number](a, b T) T {
	return a + b
}

// ------------------------------------
// MOST IMPORTANT RULE
// ------------------------------------
//
// A Go interface should answer:
//
// "What behavior does this consumer need?"
//
// not:
//
// "What interface can I create for this struct?"
//
// Good interfaces are usually:
//
// - small
// - behavior-focused
// - consumer-focused
// - easy to implement
// - easy to test
// - useful at dependency boundaries

func main() {}

60. Final Mental Model

package main

// Think about Go interfaces like this:
//
//                      Speaker
//                         |
//                  requires Speak()
//                         |
//             +-----------+-----------+
//             |           |           |
//            Dog        Person      Robot
//             |           |           |
//          Speak()     Speak()      Speak()
//
// Speaker does not care:
//
// "What ARE you?"
//
// Speaker cares:
//
// "Can you Speak()?"
//
// That leads to the core Go interface idea:
//
// ------------------------------------------------
//
// INTERFACE = REQUIRED BEHAVIOR
//
// ------------------------------------------------
//
// Struct:
//
//	type User struct {
//	    Name string
//	}
//
// answers:
//
// "What data does this value contain?"
//
// ------------------------------------------------
//
// Interface:
//
//	type Saver interface {
//	    Save() error
//	}
//
// answers:
//
// "What can this value do?"
//
// ------------------------------------------------
//
// Use interfaces especially for:
//
// - databases
// - repositories
// - HTTP clients
// - payment providers
// - email systems
// - notification services
// - caches
// - file systems
// - clocks
// - external APIs
// - loggers
// - queues
// - testing dependencies
//
// ------------------------------------------------
//
// Remember these rules:
//
// 1. Interface implementation is implicit.
//
// 2. Required method signatures must match.
//
// 3. Keep interfaces small.
//
// 4. Define interfaces around consumer needs.
//
// 5. Pointer receivers affect interface satisfaction.
//
// 6. any is interface{}.
//
// 7. Use safe type assertions when type is uncertain.
//
// 8. Type switches inspect dynamic types.
//
// 9. A nil interface differs from an interface
//    containing a typed nil pointer.
//
// 10. Interfaces and generics solve different problems.
//
// 11. Generic constraints can also be interfaces.
//
// 12. Do not create an interface for every struct.
//
// 13. Use interfaces where abstraction gives real value.
//
// 14. Prefer behavior-oriented designs.
//
// 15. Ask:
//
//     "What behavior does this code actually need?"
//
// That question is the foundation of good
// interface design in Go.

func main() {}

Related Posts

Go Tutorials: Go Methods

Go methods become much easier once you understand one idea: A method is simply a function attached to a type. And the important rule is: A receiver…

Read More

Go Tutorials: Modules, Packages, Subpackages, Submodules, and Workspaces

This tutorial gives you one complete mental model for: The most important idea is: And sometimes: These two designs are very different. 1. What is a Go…

Read More

Go Tutorials: Numeric Types Beginner Tutorial

Go has four main families of numeric types: The simplest way to remember them is: Type family Stores Example int Whole numbers, positive or negative -10, 0,…

Read More

Go Tutorials: Go Testing, Benchmarking, and Profiling

These three topics become much easier once you separate the questions they answer: Topic Main question Main Go tool Result Testing Does my code work correctly? go…

Read More

Go Tutorials: Generics

1. What are Generics? Generics let you write one piece of code that works with multiple types while keeping Go’s compile-time type safety. Suppose you want a…

Read More

Go Concurrency Management Made Simple

This tutorial covers: The goal is: One concept → one purpose → one complete runnable example. Every example is independent. Save any example as: and run: 0….

Read More