Understanding Weak Pointers in Go 1.24
Lukas Schneider
DevOps Engineer · Leapcell

In Go, a weak pointer refers to a reference that does not prevent the garbage collector (GC) from reclaiming the target object.
When an object is only referenced by weak pointers and has no strong references, the GC will still treat it as unreachable and reclaim it; afterwards, all weak pointers to it will automatically become nil
.
In short, a weak pointer does not increase an object’s reference count. When an object is referenced only by weak pointers, the garbage collector can free it. Therefore, before attempting to use the value of a weak pointer, you should check whether it is nil
.
The weak
Package in Go 1.24
Go 1.24 introduces the weak
package, which provides a concise 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 weak pointer val := wp.Value() // get strong reference or nil if val != nil { fmt.Println(val.Data) } else { fmt.Println("object has been garbage collected") } }
In the example above, weak.Make(obj)
creates a weak pointer to obj
.
When calling wp.Value()
, it returns a strong reference if the object is still alive; otherwise, it returns nil
.
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") } }
By setting the strong reference obj
to nil
and explicitly triggering GC,
you can observe that the weak pointer returns nil
once the object has been collected.
Differences Between Weak Pointers and Strong References
Impact on Garbage Collection (GC):
- Strong references keep objects alive.
- Weak references do not keep objects alive.
Nil Values:
- A strong reference is
nil
when it explicitly points to nothing. - A weak reference is
nil
usually because the target object has already been collected, or it has not yet been assigned.
Access Method:
- A strong reference can directly dereference the object.
- A weak reference requires calling the
Value()
method before accessing the object.
Example 1: Using Weak Pointers for Temporary Caching
A typical scenario for weak pointers is storing entries in a cache without preventing them from being collected by the GC.
package main import ( "fmt" "runtime" "sync" "weak" ) type User struct { Name string } var cache sync.Map // map[int]weak.Pointer[*User] func GetUser(id int) *User { // ① Try to fetch from cache first if wp, ok := cache.Load(id); ok { if u := wp.(weak.Pointer[User]).Value(); u != nil { fmt.Println("cache hit") return u } } // ② Actually load (here, just construct) u := &User{Name: fmt.Sprintf("user-%d", id)} cache.Store(id, weak.Make(u)) fmt.Println("load from DB") return u } func main() { u := GetUser(1) // load from DB fmt.Println(u.Name) runtime.GC() // even if GC runs immediately, `main` holds a strong ref, so User survives u = nil // release the last strong reference runtime.GC() // trigger GC, User may be collected _ = GetUser(1) // if collected, it will load from DB again }
In this cache implementation, entries are stored as weak pointers. If the object has no other strong references, the GC can reclaim it; on the next call to GetUser
, the data will be reloaded.
Running the code above produces the following output:
$ go run cache.go load from DB user-1 load from DB
Why Use Weak Pointers?
Common scenarios include:
- Caching: Store objects without forcing them to remain in memory; if not used elsewhere, they can be collected.
- Observer pattern: Keep references to observers without preventing them from being collected.
- Canonicalization: Ensure only one instance of the same object exists, while allowing unused ones to be collected.
- Dependency graphs: Prevent reference cycles in structures like trees or graphs.
Notes on Using Weak Pointers
- Always check for nil: Objects may be collected at any GC cycle; the result of
Value()
must not be cached. - Avoid circular dependencies: Don’t let objects referenced by weak pointers hold strong references back to the container, or cycles may still form.
- Performance trade-offs: Accessing a weak pointer requires an extra call, and frequently reloading objects that fall back to
nil
may cause churn.
Example 2: Normal Use of Strong Pointers
package main import ( "fmt" "runtime" ) type Session struct { ID string } func main() { s := new(Session) // equivalent to &Session{} s.ID = "abc123" fmt.Println("strong ref alive:", s.ID) s = nil // remove the last strong reference runtime.GC() // attempt to trigger GC (for demo only, actual timing depends on runtime) fmt.Println("done") }
Here, s
is a strong pointer. As long as it remains reachable, the Session
object will never be collected by the GC.
When Is an Object Pointed to by a Strong Pointer Collected?
-
Reachability analysis: Go uses a mark-and-sweep GC. At the start of a GC cycle, the runtime traverses all strong references from root objects (stack, global variables, current registers, etc.).
- Objects reachable through strong references are considered reachable and will stay alive.
- All others are marked unreachable and freed during the sweep phase.
-
No reference counting: Go only checks whether an object is reachable from roots. The number of references or whether values are equal does not affect collection.
-
Uncertain timing: GC cycles are triggered automatically by the scheduler. Developers can call
runtime.GC()
as a hint, but this does not guarantee immediate collection. -
Variables themselves may be collected: If a strong pointer variable
s
resides on the heap and the structure holding it is no longer reachable, thens
itself may be collected. Stack variables are freed when the function returns.
Summary
A strong pointer guarantees that the object it points to remains in the “live set” across all GC cycles. Once the last strong reference is broken, the object will be automatically released during the next GC cycle.
We are Leapcell, your top choice for hosting Go projects.
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
Multi-Language Support
- Develop with Node.js, Python, Go, or Rust.
Deploy unlimited projects for free
- pay only for usage — no requests, no charges.
Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the Documentation!
Follow us on X: @LeapcellHQ