Understanding Weak Pointers in Go
Hi, my name is Walid, a backend developer who’s currently learning Go and sharing my journey by writing about it along the way. A weak pointer is a reference to an object that does not prevent the garbage collector (GC) from reclaiming the object. If there are no strong references to the object, the GC can collect it, and any weak pointers to it will automatically become nil Why Use Weak Pointers? Common use cases include: Caching: Store objects without forcing them to remain in memory if they’re no longer used elsewhere. Observer Patterns: Hold references to observers without preventing their garbage collection. Canonicalization: Ensure only one instance of an object exists without preventing its collection when unused Dependency Graphs: Prevent cycles in structures like trees or graphs. The weak Package in Go 1.24 Go 1.24 introduces the weak package, providing a straightforward API for creating and using weak pointers. import "weak" type MyStruct struct { Data string } func main() { obj := &MyStruct{Data: "example"} wp := weak.Make(obj) // Create a weak pointer val := wp.Value() // Retrieve the strong pointer or nil if val != nil { fmt.Println(val.Data) } else { fmt.Println("Object has been garbage collected") } } In this example, weak.Make(obj) creates a weak pointer to obj. Calling wp.Value() returns the strong pointer if the object is still alive or nil if it has been collected. Practical Example: Implementing a Cache One practical application of weak pointers is in building caches that don't prevent their entries from being garbage collected. import ( "sync" "weak" ) type Data struct { Value string } var cache sync.Map // map[string]weak.Pointer[*Data] func GetData(key string) *Data { if wp, ok := cache.Load(key); ok { if data := wp.(weak.Pointer[*Data]).Value(); data != nil { return data } } // Load data from source data := &Data{Value: "fresh data"} cache.Store(key, weak.Make(data)) return data } In this cache implementation, entries are stored as weak pointers. If no other strong references to the data exist, the GC can collect it, and subsequent calls to GetData will reload the data Testing Weak Pointers import ( "fmt" "runtime" "weak" ) type MyStruct struct { Data string } func main() { obj := &MyStruct{Data: "test"} wp := weak.Make(obj) obj = nil // Remove strong reference runtime.GC() if wp.Value() == nil { fmt.Println("Object has been garbage collected") } else { fmt.Println("Object is still alive") } }

Hi, my name is Walid, a backend developer who’s currently learning Go and sharing my journey by writing about it along the way.
A weak pointer is a reference to an object that does not prevent the garbage collector (GC) from reclaiming the object. If there are no strong references to the object, the GC can collect it, and any weak pointers to it will automatically become nil
Why Use Weak Pointers?
Common use cases include:
Caching: Store objects without forcing them to remain in memory if they’re no longer used elsewhere.
Observer Patterns: Hold references to observers without preventing their garbage collection.
Canonicalization: Ensure only one instance of an object exists without preventing its collection when unused
Dependency Graphs: Prevent cycles in structures like trees or graphs.
The weak Package in Go 1.24
Go 1.24 introduces the weak package, providing a straightforward API for creating and using weak pointers.
import "weak"
type MyStruct struct {
Data string
}
func main() {
obj := &MyStruct{Data: "example"}
wp := weak.Make(obj) // Create a weak pointer
val := wp.Value() // Retrieve the strong pointer or nil
if val != nil {
fmt.Println(val.Data)
} else {
fmt.Println("Object has been garbage collected")
}
}
In this example, weak.Make(obj) creates a weak pointer to obj. Calling wp.Value() returns the strong pointer if the object is still alive or nil if it has been collected.
Practical Example: Implementing a Cache
One practical application of weak pointers is in building caches that don't prevent their entries from being garbage collected.
import (
"sync"
"weak"
)
type Data struct {
Value string
}
var cache sync.Map // map[string]weak.Pointer[*Data]
func GetData(key string) *Data {
if wp, ok := cache.Load(key); ok {
if data := wp.(weak.Pointer[*Data]).Value(); data != nil {
return data
}
}
// Load data from source
data := &Data{Value: "fresh data"}
cache.Store(key, weak.Make(data))
return data
}
In this cache implementation, entries are stored as weak pointers. If no other strong references to the data exist, the GC can collect it, and subsequent calls to GetData will reload the data
Testing Weak Pointers
import (
"fmt"
"runtime"
"weak"
)
type MyStruct struct {
Data string
}
func main() {
obj := &MyStruct{Data: "test"}
wp := weak.Make(obj)
obj = nil // Remove strong reference
runtime.GC()
if wp.Value() == nil {
fmt.Println("Object has been garbage collected")
} else {
fmt.Println("Object is still alive")
}
}