Go favors Composition over Inheritance. By embedding structs inside other structs, you automatically promote fields and methods without the rigid coupling of object-oriented class hierarchies.
TL;DR (Quick Summary)#
- Methods: Functions attached to a specific type via a Receiver clause (
func (r ReceiverType) MethodName()). - Value Receiver
(r Type): Operates on a copy of the struct. Cannot mutate original fields. - Pointer Receiver
(r *Type): Operates directly on the memory address. Can mutate struct fields and avoids copying overhead. - Struct Embedding: Anonymous struct fields promote their inner methods and fields to the outer container struct automatically.
1. Value Receiver vs Pointer Receiver#
package main
import "fmt"
type Account struct {
Owner string
Balance float64
}
// Value Receiver (Operates on a COPY - Balance NOT updated!)
func (a Account) DepositCopy(amount float64) {
a.Balance += amount
}
// Pointer Receiver (Operates on ORIGINAL memory - Balance IS updated!)
func (a *Account) Deposit(amount float64) {
a.Balance += amount
}
func main() {
acc := Account{Owner: "Rachmat", Balance: 100.0}
acc.DepositCopy(50.0)
fmt.Println("After Copy Deposit:", acc.Balance) // 100.0
acc.Deposit(50.0)
fmt.Println("After Pointer Deposit:", acc.Balance) // 150.0
}2. Struct Embedding (Composition over Inheritance)#
Instead of subclassing, Go uses Struct Embedding to promote inner struct capabilities.
package main
import "fmt"
// Base struct
type Logger struct {
Prefix string
}
func (l *Logger) Log(message string) {
fmt.Printf("[%s] %s
", l.Prefix, message)
}
// Outer struct EMBEDS Logger anonymously
type UserService struct {
Logger // Embedded struct!
DBName string
}
func main() {
service := UserService{
Logger: Logger{Prefix: "USER-SVC"},
DBName: "production_db",
}
// 🟢 Log() method is automatically PROMOTED to service!
service.Log("Initializing database connection...")
// Output: [USER-SVC] Initializing database connection...
}3. Receiver Rules Comparison#
| Scenario | Use Value Receiver (r Type) | Use Pointer Receiver (r *Type) |
|---|---|---|
| Field Mutation | No (Cannot mutate). | Yes (Mutates original fields). |
| Struct Size | Small primitive structs. | Heavy structs (Prevents copy memory overhead). |
| Concurrency | Thread-safe by value copy. | Requires mutex locks if mutated concurrently. |
| Consistency | If any method needs pointer receiver, ALL methods should use pointer receivers! |
4. Troubleshooting & Common Errors#
Error 1: cannot call pointer method on value#
The Cause: Attempting to call a pointer receiver method on an unaddressable value.
The Fix: Store the struct in a variable or pass a pointer explicitly (&MyStruct{}).
Summary & Next Steps#
In this episode:
- We defined methods using Value and Pointer Receivers.
- We analyzed when to use pointer receivers for mutation and performance.
- We composed domain objects using Struct Embedding.
In Episode 9: Implicit Interfaces & Polymorphism, we will explore how Go implements duck typing with compile-time type safety!

