Go/
Lesson

When you ask AI to "make this code flexible" or "add an abstraction layer," it reaches for interfaces. In Go, interfaces work differently than in Java or TypeScript -- types satisfy them implicitly, with no implements keyword. This is elegant but creates a unique class of AI mistakes you need to spot.

How Go interfaces work

An interface is a type that specifies method signatures. No implementation, just the contract.

type Speaker interface {
    Speak() string
}

Any type that has a Speak() string method satisfies Speaker. That's it. No declaration needed.

type Dog struct{ Name string }

func (d Dog) Speak() string {
    return "Woof! My name is " + d.Name
}

type Cat struct{ Name string }

func (c Cat) Speak() string {
    return "Meow! Call me " + c.Name
}

func main() {
    var s Speaker
    s = Dog{Name: "Rex"}
    fmt.Println(s.Speak())  // Woof! My name is Rex
    s = Cat{Name: "Whiskers"}
    fmt.Println(s.Speak())  // Meow! Call me Whiskers
}

Go checks at compile time: "Does Dog have all the methods Speaker requires?" Yes? Then Dog is a Speaker.

AI pitfall
AI trained on Java or C# will sometimes generate explicit interface declarations like comments saying "Dog implements Speaker" or try to create constructor patterns that return the interface type. In Go, there's no ceremony. If the methods match, the type satisfies the interface. When you see AI adding unnecessary boilerplate around interface satisfaction, strip it out.
02

Why small interfaces matter

The Go standard libraryWhat is standard library?A collection of ready-made tools that come built into a language - no install required. Covers common tasks like reading files or making web requests. is famous for tiny, focused interfaces:

InterfaceMethodsWhat it enables
io.ReaderRead([]byte) (int, error)Any data source: files, network, strings, gzip
io.WriterWrite([]byte) (int, error)Any data sink: files, network, buffers, HTTP
errorError() stringAny error condition with a message
fmt.StringerString() stringCustom string representation for any type

One method each. This is intentional.

AI pitfall
Ask AI to "create an interface for the database layer" and you'll get a 10-method monster. This is Java thinking. In Go, prefer multiple small interfaces over one large one:
> // AI generates this -- too rigid
> type Database interface {
>     Connect() error
>     Query(string) ([]Row, error)
>     Insert(Row) error
>     Update(Row) error
>     Delete(int) error
>     Close() error
> }
>
> // Idiomatic Go -- small, composable
> type Querier interface {
>     Query(string) ([]Row, error)
> }
>
> type Inserter interface {
>     Insert(Row) error
> }
>

Functions that only read should accept Querier, not the full Database. This makes testing trivial and keeps coupling low.
03

Evaluating AI interface design

When AI generates interfaces, run through this checklist:

CheckGood signBad sign
Number of methods1-35+ (probably too broad)
Defined where consumedYes, near the function that uses itNo, in a separate "interfaces" package
Named after behaviorReader, Validator, HandlerIUserService, AbstractManager
Used by at least 2 typesYesNo (premature abstraction)
Returns concrete typesFunctions return structsFunctions return interfaces (usually wrong)

The last point deserves emphasis: accept interfaces, return structs.

// Good: accepts any io.Reader, returns concrete type
func ReadConfig(r io.Reader) (*Config, error) { ... }

// Bad: returns an interface (hides what you actually get)
func NewReader() io.Reader { ... }
AI frequently violates "accept interfaces, return structs" by creating factory functions that return interface types. This hides the concrete type from callers and makes the code harder to debug. When you see func New() SomeInterface, question whether it should return the concrete struct instead.
04

Interface composition

You can embed interfaces within other interfaces to compose behavior:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

This is how io.ReadWriteCloser is defined in the standard libraryWhat is standard library?A collection of ready-made tools that come built into a language - no install required. Covers common tasks like reading files or making web requests.. Any type implementing both Read and Write automatically satisfies ReadWriter.

05

The nil interface trap

An interface value is actually a pair: (concrete type, concrete value). This creates one of Go's most infamous gotchas:

type Thing struct{}

func (t *Thing) Method() {}

var thing *Thing = nil
var i interface{} = thing

fmt.Println(i == nil)  // false! The interface holds (*Thing, nil)

The interface knows the type (*Thing) even though the value is nil. So the interface itself isn't nil.

AI pitfall
AI generates this buggy pattern constantly:
> func GetThing() error {
>     var err *MyError = nil
>     // ... some logic that might set err ...
>     return err  // Returns (*MyError, nil) -- NOT a nil error!
> }
>
> if err := GetThing(); err != nil {
>     // This executes even when there's no error!
> }
>

The fix: return nil directly, not a typed nil variable:
> func GetThing() error {
>     // ... some logic ...
>     return nil  // Returns (nil, nil) -- truly nil interface
> }
>

This bug is subtle enough that even experienced developers miss it. When reviewing AI code that returns interfaces, check whether any code path returns a typed nil.
06

The empty interface and any

The empty interface interface{} (or its alias any since Go 1.18) matches every type:

func AcceptAnything(x any) {
    fmt.Printf("Received: %v (type: %T)\n", x, x)
}

AcceptAnything(42)           // int
AcceptAnything("hello")      // string
AcceptAnything([]int{1,2,3}) // slice

You'll see any in JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it. unmarshaling, generic containers, and reflection code. It's powerful but throws away type safety.

07

Common interface patterns

The error interface

The built-in error type is an interface. Any type with an Error() string method qualifies:

type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error in %s: %s", e.Field, e.Message)
}

func ValidateAge(age int) error {
    if age < 0 {
        return &ValidationError{Field: "age", Message: "cannot be negative"}
    }
    return nil
}

Sorting with sort.Interface

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

people := []Person{{"Alice", 30}, {"Bob", 25}}
sort.Sort(ByAge(people))
08

Quick reference

ConceptSyntaxExample
Define interfacetype Name interface { methods }type Speaker interface { Speak() string }
Satisfy interfaceJust define the methodsfunc (d Dog) Speak() string { ... }
Empty interfaceany or interface{}func Print(v any)
Interface compositionEmbed interfacestype ReadWriter interface { Reader; Writer }
Type assertionvalue := i.(ConcreteType)d := s.(Dog)
Safe type assertionvalue, ok := i.(ConcreteType)d, ok := s.(Dog)