July 7, 2026

Go Structs and Methods – Organizing Data and Behavior

Updated July 2, 2026. Refreshed for Go 1.22+ and current SEO best practices.

In this lesson we cover Go structs and methods — the primary way to model real-world entities in Go. If you completed Go Arrays, Slices, and Maps, you know how to store collections. Now you will learn how to group related fields into a single type and attach functions to that type. This Go structs tutorial builds the foundation for interfaces, error handling, and the REST API you will build in Lesson 12.

Prerequisites: Lessons 1–7 completed, Go 1.22 or newer installed. Estimated time: 45–60 minutes.

1. Defining a Struct

A struct is a composite type that groups named fields together. Each field has a name and a type. Structs are Go’s answer to objects in other languages — but without class inheritance. You define a struct with the type keyword:

package main

import "fmt"

// Person is a struct with three fields
type Person struct {
	Name string
	Age  int
	City string
}

func main() {
	var p Person
	fmt.Println(p) // { 0 } — zero value: empty string, 0 for int

	p.Name = "Ada"
	p.Age = 28
	p.City = "Lagos"
	fmt.Println(p.Name, p.Age, p.City)
}

Field names that start with an uppercase letter are exported (visible outside the package). Lowercase field names are private to the package — the same export rule you learned for identifiers in the Basic Syntax lesson.

2. Struct Literals

Create struct values with literals. You can list fields in order (positional) or by name (recommended for clarity):

package main

import "fmt"

type Book struct {
	Title  string
	Author string
	Pages  int
}

func main() {
	// Named field literal (preferred)
	b1 := Book{
		Title:  "The Go Programming Language",
		Author: "Donovan & Kernighan",
		Pages:  380,
	}

	// Positional literal — must supply all fields in order
	b2 := Book{"Learning Go", "Jon Bodner", 350}

	// Pointer to a struct with &
	b3 := &Book{Title: "Concurrency in Go", Author: "Katherine Cox-Buday", Pages: 300}

	fmt.Println(b1.Title)
	fmt.Println(b2.Author)
	fmt.Println(b3.Pages)
}

Named field literals are safer — they survive field reordering and make code self-documenting. Use &Type{...} when you need a pointer to a struct, which is common when passing structs to functions that should modify them.

3. Methods — Functions with a Receiver

A method is a function with a receiver — a special parameter that binds the function to a type. Methods let you write p.Greet() instead of Greet(p), which reads more naturally for types that represent entities.

package main

import "fmt"

type Rectangle struct {
	Width  float64
	Height float64
}

// Area is a method with a value receiver
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

// Scale is a method with a pointer receiver
func (r *Rectangle) Scale(factor float64) {
	r.Width *= factor
	r.Height *= factor
}

func main() {
	rect := Rectangle{Width: 10, Height: 5}
	fmt.Println("Area:", rect.Area()) // 50

	rect.Scale(2)
	fmt.Println("Scaled area:", rect.Area()) // 200
}

The receiver appears between func and the method name. It looks like an extra parameter, but it is not part of the call — rect.Area() passes rect implicitly as the receiver.

4. Value Receivers vs Pointer Receivers

Choosing between a value receiver (r Rectangle) and a pointer receiver (r *Rectangle) is one of the most important decisions in Go struct design:

  • Value receiver — receives a copy of the struct. Methods cannot modify the original. Good for small structs and read-only operations.
  • Pointer receiver — receives a pointer to the struct. Methods can modify the original. Good for large structs (avoids copying) and mutating operations.
Aspect Value Receiver Pointer Receiver
Syntax func (r Rect) M() func (r *Rect) M()
Can modify struct? No (works on a copy) Yes (modifies original)
Memory Copies entire struct each call Copies only a pointer (8 bytes)
Nil receiver Not applicable Can panic if receiver is nil
Best for Small structs, getters, String() Mutators, large structs, consistency

Consistency rule: If any method on a type uses a pointer receiver, use pointer receivers for all methods on that type. This avoids confusion about whether a method mutates the struct or not.

package main

import "fmt"

type Counter struct {
	Count int
}

// Pointer receiver — modifies the original
func (c *Counter) Increment() {
	c.Count++
}

// Pointer receiver for consistency, even though it only reads
func (c *Counter) String() string {
	return fmt.Sprintf("Count: %d", c.Count)
}

func main() {
	c := Counter{}
	c.Increment()
	c.Increment()
	fmt.Println(c) // Count: 2
}

5. Struct Embedding — Composition Over Inheritance

Go does not have class inheritance. Instead, it uses embedding — placing one struct type inside another anonymously. The embedded struct’s fields and methods are promoted to the outer type, so you can access them directly.

package main

import "fmt"

type Address struct {
	Street string
	City   string
}

func (a Address) FullAddress() string {
	return a.Street + ", " + a.City
}

type Employee struct {
	Name    string
	Address // embedded — fields promoted to Employee
	Salary  float64
}

func main() {
	emp := Employee{
		Name: "Chidi",
		Address: Address{
			Street: "12 Broad Street",
			City:   "Lagos",
		},
		Salary: 75000,
	}

	// Promoted fields — no need for emp.Address.City
	fmt.Println(emp.City)
	fmt.Println(emp.FullAddress()) // promoted method
}

Embedding is how Go achieves code reuse without inheritance hierarchies. A type can embed multiple structs. If two embedded types have a field with the same name, you must qualify the access: emp.Address.City.

Embedding also works with interfaces — a struct that embeds an interface must satisfy that interface at runtime. You will explore this pattern in the Interfaces lesson.

6. Putting It Together — A Simple Bank Account

Here is a practical example that combines struct definition, literals, methods, and pointer receivers:

package main

import (
	"fmt"
	"log"
)

type Account struct {
	Owner   string
	Balance float64
}

func NewAccount(owner string) *Account {
	return &Account{Owner: owner, Balance: 0}
}

func (a *Account) Deposit(amount float64) error {
	if amount <= 0 {
		return fmt.Errorf("deposit amount must be positive")
	}
	a.Balance += amount
	return nil
}

func (a *Account) Withdraw(amount float64) error {
	if amount <= 0 {
		return fmt.Errorf("withdrawal amount must be positive")
	}
	if amount > a.Balance {
		return fmt.Errorf("insufficient funds")
	}
	a.Balance -= amount
	return nil
}

func (a *Account) String() string {
	return fmt.Sprintf("%s: $%.2f", a.Owner, a.Balance)
}

func main() {
	acct := NewAccount("Ngozi")
	if err := acct.Deposit(500); err != nil {
		log.Fatal(err)
	}
	if err := acct.Withdraw(150); err != nil {
		log.Fatal(err)
	}
	fmt.Println(acct) // Ngozi: $350.00
}

Notice the constructor pattern NewAccount — a common Go idiom that returns a pointer to a newly initialized struct. Error returns on Deposit and Withdraw preview the error-handling style you will master in Lesson 10.

7. Next Steps

You now understand Go structs and methods — defining types, creating literals, writing methods with value and pointer receivers, and composing types through embedding. In the next lesson we dive deeper into pointers — when to use them, how they interact with structs, and common patterns you will see in production Go code.

Continue the series: Lesson 7 – Arrays, Slices, and Maps · Lesson 9 – Pointers

Frequently Asked Questions

What is a struct in Go?
A struct groups related fields into a single type — Go’s primary way to model data. Structs do not have inheritance; use embedding for composition.

What is the difference between value and pointer receivers?
Pointer receivers can modify the struct and avoid copying large values. Value receivers copy the struct and cannot change the original. Use pointer receivers when methods mutate state or for consistency across all methods on a type.

What is struct embedding in Go?
Embedding one struct inside another anonymously promotes its fields and methods to the outer type — Go’s composition pattern instead of class inheritance.

Want live Go or backend classes? Join Alkademy for instructor-led programming courses with hands-on projects.


0 0 votes
Article Rating
Subscribe
Notify of
1 Comment
Oldest
Newest Most Voted
trackback

[…] Previous: Lesson 8: Go Structs and Methods – Organizing Data and Behavior […]

1
0
Would love your thoughts, please comment.x
()
x