Go/
Lesson

Every time you ask AI to "add behavior to this struct," it generates methods. Methods are how Go attaches functions to types -- they're the closest thing Go has to class methods, but without classes. Your job is knowing when AI picked the right receiver type, because it frequently doesn't.

What makes a method different from a function

A regular function stands alone. A method has a receiver that ties it to a specific type.

When you ask AI to "create a User type with a full name method," it generates something like:

type User struct {
    FirstName string
    LastName  string
}

func (u User) FullName() string {
    return u.FirstName + " " + u.LastName
}

// Usage
user := User{FirstName: "Alice", LastName: "Chen"}
fmt.Println(user.FullName())  // Output: Alice Chen

The receiver (u User) sits between func and the method name. It says: "This method belongs to the User type, and u holds the specific instance when called."

02

Value receivers vs pointer receivers

This is where AI makes the most mistakes. Go gives you two receiver types, and picking wrong creates silent bugs.

Value receivers: the copy trap

A value receiver makes a copy of the struct when the method is called:

func (u User) SetName(newName string) {
    u.FirstName = newName  // Modifies the copy, not the original
}

user := User{FirstName: "Alice"}
user.SetName("Bob")
fmt.Println(user.FirstName)  // Still "Alice" -- the change vanished
AI pitfall
Ask AI to "add a method that updates the user's email" and it will frequently generate a value receiver. The code compiles, tests might even pass if they only check the return value, but the mutation silently disappears. Always check: does this method modify state? If yes, it needs a pointer receiver.

Pointer receivers: mutations that stick

A pointer receiver gets the address of the struct, letting you modify the original:

func (u *User) SetName(newName string) {
    u.FirstName = newName  // Modifies the original struct
}

user := User{FirstName: "Alice"}
user.SetName("Bob")
fmt.Println(user.FirstName)  // "Bob" -- change persists
03

Deciding which receiver to use

When reviewing AI-generated methods, use this decision table:

QuestionValue receiverPointer receiver
Does the method modify the struct?Never correctRequired
Is the struct large (many fields)?Wasteful (copies data)Efficient (copies pointer)
Does any other method on this type use a pointer?InconsistentUse pointer for all
Is the struct small and read-only?FineUnnecessary
Does it need to be called on a nil receiver?PanicsCan check for nil

The consistency rule matters most in practice: if any method on a type needs a pointer receiver, all methods on that type should use pointer receivers. AI almost never follows this convention on its own.

// AI often generates this mixed style -- it's wrong
type Counter struct {
    value int
}

func (c Counter) Get() int {      // Value receiver
    return c.value
}

func (c *Counter) Increment() {   // Pointer receiver
    c.value++
}

The fix: make Get use *Counter too. Mixed receivers create confusing behavior when values are copied.

Edge case
nil pointer receivers**
Methods can be called on nil pointers without panicking, as long as the method checks:
> func (u *User) Name() string {
>     if u == nil {
>         return ""
>     }
>     return u.FirstName
> }
>

This pattern appears in the standard library (like error implementations). AI sometimes generates nil-safe methods when it shouldn't, or omits nil checks when it should. Evaluate based on whether "no value" is a valid state for your type.
04

Methods on non-struct types

AI will occasionally generate methods on custom types based on primitives. This is a legitimate and powerful pattern:

type Celsius float64

func (c Celsius) ToFahrenheit() float64 {
    return float64(c)*9/5 + 32
}

func (c *Celsius) Add(delta float64) {
    *c += Celsius(delta)
}

temp := Celsius(25)
fmt.Println(temp.ToFahrenheit())  // 77
temp.Add(5)
fmt.Println(temp)  // 30

This is powerful for domain modeling. Instead of passing around raw float64, a Celsius type with methods makes the APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. self-documenting.

You cannot define methods on types from other packages or on built-in types directly. You must create a named type:
> type ID int  // Valid -- new named type
> // func (i int) String() string {}  // Invalid -- cannot extend built-in types
>
05

Common patterns AI generates

The String() method

When you ask AI to "make this type printable," it implements fmt.Stringer:

type Point struct {
    X, Y int
}

func (p Point) String() string {
    return fmt.Sprintf("Point(%d, %d)", p.X, p.Y)
}

pt := Point{X: 3, Y: 4}
fmt.Println(pt)  // Output: Point(3, 4)
AI pitfall
AI sometimes puts String() on a pointer receiver (func (p *Point) String() string). This means fmt.Println(point) won't use your custom format unless point is a pointer. For String(), value receivers are almost always correct.

Builder pattern

When you ask AI to "make a fluent APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. for configuration," expect:

type Config struct {
    Host string
    Port int
}

func (c *Config) WithHost(host string) *Config {
    c.Host = host
    return c
}

func (c *Config) WithPort(port int) *Config {
    c.Port = port
    return c
}

config := &Config{}
config.WithHost("localhost").WithPort(8080)

Pointer receivers are correct here because each call mutates and returns the same instance.

06

Methods vs functions: when to use each

Use caseFunctionMethod
Works with a specific type's dataNoYes
Generic utility (works with any string, int, etc.)YesNo
Needs to modify the original structOnly with pointer paramYes, with pointer receiver
Part of a type's core behaviorNoYes
Standalone logic unrelated to a typeYesNo
07

Quick reference

SyntaxWhat it doesWhen to use
func (t Type) Name() {}Value receiver -- gets a copyRead-only operations, small structs
func (t *Type) Name() {}Pointer receiver -- gets addressModifying data, large structs, consistency
func (t Type) String() stringString representationFor fmt.Println, debugging