What does this program print? Explain how finalizers behave.
Question 354HardGo 1.22 to 1.25
type Res struct{ name string }
func main() {
r := &Res{name: "db"}
runtime.SetFinalizer(r, func(r *Res) { fmt.Println("finalized", r.name) })
r = nil
runtime.GC()
fmt.Println("done")
}
Answer: done is guaranteed. finalized db may or may not print, before or after done. Finalizers run later on a single dedicated goroutine, and the program does not wait for them when it exits. Adding time.Sleep makes it likely to print, but still not guaranteed.
Finalizer gotchas:
- Never guaranteed to run: not at exit, and not for objects in the tiny allocator or package-level variables. They are not a replacement for
Close(). - They resurrect the object: the finalizer gets the pointer, so freeing it takes at least two GC cycles.
- Cycles leak: if objects in a reference cycle have finalizers, the cycle is never collected.
- Only one finalizer per object, and it must be set on the start of an allocation, not an interior pointer.
- All finalizers share one goroutine, so a slow or blocking finalizer stalls the rest.
- The object can be finalized while one of its methods is still running. See
runtime.KeepAlive.
What the interviewer is looking for: finalizers are a leak-detection safety net (the os.File pattern), never your primary cleanup. Since Go 1.24, prefer runtime.AddCleanup.
More on Memory, GC & Runtime Internals
- Q352Why is keeping a pointer as
uintptrdangerous even if the object is still referenced elsewhere? - Q353How do you do zero-copy
[]byte↔stringconversion correctly, and what can go wrong? - Q355What is
runtime.AddCleanupand why is it preferred overSetFinalizer? - Q356What are weak pointers in Go and when would you use them?
- Q357What does
runtime.KeepAlivedo and when is it required? - Q358What does this print, and what memory problem does it illustrate?