Structs & Methods

Define custom types with structs, attach methods, and understand pointer vs value receivers.

Beginner 35 min read 🐹 Go

Defining Structs

Structs are Go's way to create custom types. They group related data together — like a class in other languages, but without inheritance. Go favors composition over inheritance.

type User struct {
    Name  string
    Email string
    Age   int
}

// Create instances
u1 := User{Name: "Alice", Email: "[email protected]", Age: 30}
u2 := User{"Bob", "[email protected]", 25} // positional (fragile, avoid)
var u3 User // zero value: {"", "", 0}

fmt.Println(u1.Name)  // Alice
u1.Age = 31           // modify field
fmt.Println(u1)
Output
Alice
{Alice [email protected] 31}

Methods — Value vs Pointer Receivers

Methods are functions attached to a type. The receiver determines whether the method gets a copy (value receiver) or a reference (pointer receiver):

type Rect struct {
    Width, Height float64
}

// Value receiver: gets a COPY, can't modify original
func (r Rect) Area() float64 {
    return r.Width * r.Height
}

// Pointer receiver: gets a REFERENCE, can modify original
func (r *Rect) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

func (r Rect) String() string {
    return fmt.Sprintf("Rect(%.0fx%.0f)", r.Width, r.Height)
}

r := Rect{10, 5}
fmt.Println(r.Area())  // 50
r.Scale(2)
fmt.Println(r)         // Rect(20x10)
fmt.Println(r.Area())  // 200
Output
50
Rect(20x10)
200
ReceiverSyntaxWhen to Use
Value (r Rect)Gets a copyRead-only methods, small structs
Pointer (r *Rect)Gets a referenceMethods that modify, large structs
Key Takeaway: Use pointer receivers when the method needs to modify the struct OR the struct is large (avoids copying). Be consistent — if any method uses a pointer receiver, all should.

Embedding (Composition)

Go doesn't have inheritance. Instead, you embed one struct inside another. The embedded struct's fields and methods are promoted to the outer struct:

type Address struct {
    Street, City, Country string
}

type Employee struct {
    Name string
    Address  // Embedded (no field name = promoted)
    Salary float64
}

e := Employee{
    Name:    "Alice",
    Address: Address{Street: "123 Main St", City: "NYC", Country: "US"},
    Salary:  95000,
}

// Access promoted fields directly
fmt.Println(e.City)     // NYC (promoted from Address)
fmt.Println(e.Address)  // {123 Main St NYC US}

Composition over Inheritance

Go deliberately omits inheritance. Instead of "Employee IS A Person", Go says "Employee HAS A Address". This avoids the fragile base class problem and deep inheritance hierarchies. Embed what you need, compose behavior with interfaces.

Value vs Pointer Receiver
Value (s Rect)
Gets a copy, can't modify
Pointer (s *Rect)
Gets pointer, can modify
Convention
Use pointer if method mutates

Practice Exercises

Easy Hello World Variant

Modify the example to accept user input and print a personalized greeting.

Easy Code Reading

Read through the code examples above and predict the output before running them.

Medium Extend the Example

Take one code example and add error handling, input validation, or a new feature.