Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 17

    Interfaces

    Interfaces define behavior contracts — a set of method signatures.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Interfaces define behavior contracts — a set of method signatures. Go interfaces are satisfied implicitly: no implements keyword. This enables decoupling, testability with mocks, and the accept-interfaces-return-structs API design principle.

    The empty interface any (Go 1.18+) accepts all types — use sparingly. Small interfaces (io.Reader, io.Writer with one method) are idiomatic. Understanding nil interface vs nil concrete value in interface is a classic interview trap.

    Interfaces power dependency injection, HTTP handlers, database drivers, and plugin architectures throughout the Go ecosystem.

    The story

    Google's internal RPC layers depend on small interfaces: an io.Reader for streaming uploads, a StorageBackend with Put and Get for swapping GCS mocks in tests. Uber's dependency injection wires concrete implementations behind interfaces so integration tests replace Kafka with an in-memory bus.

    Go interfaces are satisfied implicitly — no implements keyword — enabling decoupling without inheritance hierarchies that plague larger codebases.

    Understanding the topic

    Key concepts

    • type Reader interface { Read(p []byte) (n int, err error) }.
    • Implicit satisfaction — no declaration needed on implementing type.
    • Interface value holds (type, value) pair — nil interface vs typed nil.
    • Empty interface any — accepts everything; prefer generics or union constraints.
    • Type assertion: v := i.(ConcreteType) or v, ok := i.(ConcreteType).
    • Type switch on interface values for polymorphic dispatch.
    text
    classDiagram
    class io.Reader {
    <<interface>>
    +Read(p []byte)
    }
    class os.File {
    +Read(p []byte)
    }
    io.Reader <|.. os.File

    Step-by-step explanation

    1. Define interface with method signatures.
    2. Concrete type implementing all methods satisfies interface automatically.
    3. Assign concrete to interface variable — dynamic dispatch at runtime.
    4. Interface with nil concrete value is not nil interface if type set.
    5. Compile-time check: var _ MyInterface = (*MyType)(nil).
    6. Embed interfaces in interfaces for composition.

    Practical code example

    Payment processor interface with mock implementation for testing:

    go
    package main
    import "fmt"
    type PaymentProcessor interface {
    Charge(amount float64, currency string) (string, error)
    }
    type StripeProcessor struct{}
    func (StripeProcessor) Charge(amount float64, currency string) (string, error) {
    return fmt.Sprintf("stripe_ch_%f_%s", amount, currency), nil
    }
    type MockProcessor struct{}
    func (MockProcessor) Charge(amount float64, currency string) (string, error) {
    return "mock_charge_id", nil
    }
    func checkout(processor PaymentProcessor, amount float64) {
    id, err := processor.Charge(amount, "USD")
    if err != nil {
    fmt.Println("failed:", err)
    return
    }
    fmt.Println("charged:", id)
    }
    func main() {
    checkout(StripeProcessor{}, 99.99)
    checkout(MockProcessor{}, 1.00)
    var _ PaymentProcessor = StripeProcessor{}
    }

    Line-by-line code explanation

    • type StorageBackend interface { Put(key string, data []byte) error } defines a minimal contract.
    • type GCSBackend struct{} implements the interface by having matching methods — no declaration needed.
    • func Upload(b StorageBackend, key string, data []byte) accepts any implementation.
    • interface{} or any holds values of any type — use sparingly; prefer generics or concrete types.
    • type ReadWriter interface { io.Reader; io.Writer } embeds interfaces to compose larger contracts.
    • nil interface pitfall — a typed nil pointer stored in an interface is not equal to nil.
    • var _ StorageBackend = (*MockStorage)(nil) compile-time assertion that MockStorage implements the interface.
    • type Stringer interface { String() string } — fmt printing uses this ubiquitous interface.

    Key takeaway: var _ Interface = Type{} compile-time assertion. Small interfaces enable easy mocking. Accept PaymentProcessor interface in checkout function.

    Real-world use

    Where you'll use this in production

    • Repository interfaces for database swap in tests.
    • io.Reader/io.Writer streaming in HTTP and file processing.
    • http.Handler ServeHTTP for middleware chains.
    • Plugin systems loading implementations at runtime.

    Best practices

    • Keep interfaces small — 1-3 methods (interface segregation).
    • Define interfaces at consumer, not producer side.
    • Accept interfaces, return concrete types in public APIs.
    • Use compile-time assertion: var _ IF = (*T)(nil).
    • Avoid any — use generics or specific interfaces.
    • Document expected behavior and error semantics.

    Common mistakes

    • Returning nil concrete pointer in interface — not equal to nil interface.
    • Giant interfaces with 10+ methods — hard to mock and implement.
    • Defining interfaces before concrete types — consumer-driven interfaces.
    • Type assertion without ok — panics on wrong type.
    • Overusing any losing type safety.

    Advanced interview questions

    Q1BeginnerExplicit vs implicit interface satisfaction?
    Go implicit — type satisfies if it has all methods; no implements declaration.
    Q2Beginnernil interface gotcha?
    Interface is nil only if both type and value nil; var p *T = nil; var i interface{} = p — i != nil.
    Q3IntermediateAccept interfaces return structs?
    Parameters as interfaces for flexibility; return concrete for callers to use full API.
    Q4IntermediateType assertion vs type switch?
    Assertion for single type; switch for multiple concrete types from one interface.
    Q5AdvancedDesign notification system with Email, SMS, Push — add Slack without caller change.
    Notifier interface with Send(); register implementations in slice; caller loops Notifiers — Open/Closed principle.

    Summary

    Interfaces define behavior; satisfaction is implicit. Small consumer-defined interfaces enable testing and decoupling. Beware nil interface vs typed nil pointer in interface. Type assertions and switches extract concrete types safely. Next lesson: pointers — explicit memory addresses.

    Ready to mark this lesson complete?Track your journey across the entire course.