Garbage Collection
Unreal Engine's garbage collector — how it traces UObject references, keeping objects alive, weak references, ownership, and debugging dangling-pointer crashes.
16 questions
JuniorTheoryVery commonHow do you check whether a UObject is still valid?
How do you check whether a UObject is still valid?
Use IsValid(Obj) — it checks the pointer is non-null and the object is not pending kill or garbage. For a TWeakObjectPtr, call .IsValid(). A plain non-null check is not enough, since the pointer may dangle.
Common mistakes
- ✗Relying on a
!= nullptrcheck alone — a non-UPROPERTY()pointer can dangle - ✗Thinking
IsValid()needs a GC pass to be accurate - ✗Using
IsValidLowLevel()as a general-purpose validity check in game code
Follow-up questions
- →Why can a raw
UObject*be non-null yet still invalid? - →What does
IsValid()actually inspect beyond the null check?
JuniorTheoryVery commonWhat is the garbage-collection root set in Unreal?
What is the garbage-collection root set in Unreal?
The root set is the UObjects the garbage collector treats as always reachable, such as rooted objects and key engine objects. GC starts there and keeps anything reachable from it; unreachable objects are destroyed.
Common mistakes
- ✗Thinking every existing UObject is automatically in the root set
- ✗Assuming the root set is static and cannot change while the game runs
- ✗Confusing the root set itself with all objects reachable from it
Follow-up questions
- →How does a
UPROPERTY()pointer keep an object reachable from the root? - →What does
AddToRoot()do in relation to the root set?
MiddleTheoryVery commonWhat is the difference between strong and weak references in Unreal?
What is the difference between strong and weak references in Unreal?
A strong reference (UPROPERTY() raw pointer or TObjectPtr) keeps the target alive and is auto-nulled if it dies. A weak reference (TWeakObjectPtr) never keeps it alive and reports invalidity, so you must check before use.
Common mistakes
- ✗Thinking a
TWeakObjectPtrkeeps its target alive - ✗Assuming a weak pointer is auto-nulled — it reports stale, the slot isn't a writable null
- ✗Believing strong vs weak is just about editor visibility
Follow-up questions
- →Why doesn't a
TWeakObjectPtrcreate a circular-reference leak? - →How does a strong
TObjectPtrbehave differently from a rawUPROPERTY()pointer?
JuniorTheoryCommonWhat is a TWeakObjectPtr and when do you use one?
What is a TWeakObjectPtr and when do you use one?
TWeakObjectPtr is a non-owning pointer to a UObject that does not prevent garbage collection. Use it to reference an object you do not own, then check IsValid() — after the target dies it reports invalid, not dangling.
Common mistakes
- ✗Believing a TWeakObjectPtr keeps its target object alive against GC
- ✗Accessing the pointer without first checking IsValid() on it
- ✗Using a weak pointer for an object that the class actually owns
Follow-up questions
- →How does
TWeakObjectPtrdiffer from aUPROPERTY()pointer? - →What does
IsValid()return after the target object is destroyed?
MiddleTheoryCommonWhat is AddToRoot() and why is abusing it dangerous?
What is AddToRoot() and why is abusing it dangerous?
AddToRoot() adds a UObject directly to the GC root set, so it survives until you call RemoveFromRoot(). Abusing it creates permanent leaks: rooted objects and everything they reference are never collected.
Common mistakes
- ✗Thinking rooting is temporary or auto-expires — it persists until
RemoveFromRoot() - ✗Forgetting that rooting also keeps everything the object references alive
- ✗Using
AddToRoot()where aUPROPERTY()member would suffice
Follow-up questions
- →How would you detect an object that was rooted and never removed?
- →When is
AddToRoot()genuinely the right tool over aUPROPERTY()?
MiddleTheoryCommonHow can circular references happen in Unreal Engine?
How can circular references happen in Unreal Engine?
Two UObjects holding strong UPROPERTY() pointers to each other form a cycle. Unlike reference counting, UE's mark-and-sweep GC still collects the cycle if it is unreachable from the root set, so it is not leaked.
Common mistakes
- ✗Thinking UE leaks reference cycles like a reference-counted system
- ✗Believing cycles only matter between Actors or get cleared on map load
- ✗Confusing a weak back-pointer (the fix) with the cause of cycles
Follow-up questions
- →Why is mark-and-sweep immune to cycle leaks that reference counting suffers from?
- →When would a strong-strong cycle still cause a practical problem?
MiddleTheoryCommonWhat are common causes of dangling pointers in Unreal?
What are common causes of dangling pointers in Unreal?
Storing a UObject* without UPROPERTY() (the GC can't null it), caching a raw pointer across frames, or using an Actor after Destroy(). Non-UObject classes holding raw UObject* are another frequent source.
Common mistakes
- ✗Thinking a
UPROPERTY()pointer causes dangling — it actually prevents it - ✗Believing
NewObjectreuses memory in a way that aliases live pointers - ✗Assuming an Actor pointer is safe to keep after calling
Destroy()
Follow-up questions
- →How does
UPROPERTY()turn a potential dangle into a safe null? - →Why is a non-
UObjectclass holding aUObject*especially risky?
MiddleTheoryCommonHow does Destroy() on an Actor differ from C++ delete?
How does Destroy() on an Actor differ from C++ delete?
Destroy() marks the Actor for removal — it leaves the world, but memory is freed later by the GC, not instantly. delete frees memory at once; calling it on a UObject corrupts GC state, so never delete a UObject.
Common mistakes
- ✗Thinking
Destroy()frees memory immediately - ✗Believing
deleteon aUObjectis acceptable in any case - ✗Assuming an Actor pointer is safe to use right after
Destroy()
Follow-up questions
- →Between
Destroy()and the actual deallocation, is the Actor still valid? - →Why does the engine forbid
deleteon aUObjectrather than just discouraging it?
MiddleTheoryCommonHow does the Unreal Engine garbage collector work internally?
How does the Unreal Engine garbage collector work internally?
The GC is a periodic mark-and-sweep tracer. Starting from a root set, it follows UPROPERTY() references to mark all reachable UObjects; anything unreachable is marked pending kill and destroyed. It runs on a timer, not per allocation.
Common mistakes
- ✗Thinking UE GC uses reference counting like
shared_ptr - ✗Believing the GC scans raw pointers rather than reflected
UPROPERTY()references - ✗Assuming collection happens immediately when an object becomes unreachable
Follow-up questions
- →What is the root set, and how does an object get into it?
- →How does the engine make a full GC pass cheap enough to fit a frame?
MiddleTheoryCommonHow do you prevent a UObject from being garbage collected?
How do you prevent a UObject from being garbage collected?
Keep it reachable from the root set: store it in a UPROPERTY(), add it to a TArray<TObjectPtr<>> member, or call AddToRoot() to make it a root directly. An object with no strong reference path is collected.
Common mistakes
- ✗Thinking a plain
UObject*keeps an object alive — onlyUPROPERTY()references count - ✗Confusing
MarkPendingKill()(kill it) with keeping it alive - ✗Believing a
TWeakObjectPtrprevents collection
Follow-up questions
- →When would you choose
AddToRoot()over aUPROPERTY()reference? - →How does a
UObjectcreated withNewObjectavoid being collected before you store it?
MiddleTheoryCommonDoes a UPROPERTY() pointer always keep its target object alive?
Does a UPROPERTY() pointer always keep its target object alive?
No. A UPROPERTY() strong pointer keeps the target alive only while the owning object is itself reachable. It also doesn't apply to weak UPROPERTY() members like TWeakObjectPtr, which never keep anything alive.
Common mistakes
- ✗Thinking a
UPROPERTY()pointer is a permanent root regardless of its owner - ✗Forgetting that a
UPROPERTY()TWeakObjectPtris weak and does not keep anything alive - ✗Assuming a non-null pointer alone protects an object from GC
Follow-up questions
- →If the owning object is collected, what happens to objects it referenced via
UPROPERTY()? - →How does the reachability chain from the root set down to a leaf object work?
MiddleTheoryCommonWhen should you use TWeakObjectPtr instead of a strong reference?
When should you use TWeakObjectPtr instead of a strong reference?
Use it when you want to reference an object without controlling its lifetime — caches, observers, or back-pointers to an owner. It avoids circular-reference leaks and lets you safely detect when the target has been destroyed.
Common mistakes
- ✗Using
TWeakObjectPtreverywhere expecting a performance gain - ✗Thinking a weak pointer extends lifetime across level loads
- ✗Confusing weak-vs-strong choice with thread-safety concerns
Follow-up questions
- →How do you safely dereference a
TWeakObjectPtrbefore using the target? - →Why is a child holding a strong
UPROPERTY()pointer back to its parent a problem?
JuniorTheoryOccasionalWhat does UE garbage collection manage — does it cover raw new?
What does UE garbage collection manage — does it cover raw new?
GC only manages UObject-derived types (Actors, components, UObjects). Memory from raw new, malloc, or non-UObject structs like FString is invisible to it — you must free that yourself or use a smart pointer.
Common mistakes
- ✗Thinking GC frees raw
new/mallocallocations - ✗Assuming
FString,TArrayand plainUSTRUCTs are garbage collected - ✗Believing leaking memory is impossible in an Unreal project
Follow-up questions
- →How is the lifetime of a plain
USTRUCTmember managed? - →Why can a non-
UObjectclass still safely hold aUObjectreference?
MiddleTheoryOccasionalWhat is object ownership in Unreal Engine and what is the Outer?
What is object ownership in Unreal Engine and what is the Outer?
Every UObject has an Outer — the object that owns it, set at creation via NewObject. The Outer chain defines scope and naming; if the Outer is collected, its inner objects lose reachability and are collected too.
Common mistakes
- ✗Confusing the Outer with a reference-counting owner
- ✗Thinking the Outer is just an editor label with no lifetime effect
- ✗Assuming an object can outlive its Outer
Follow-up questions
- →How do you choose the right Outer when calling
NewObject? - →What is the difference between the Outer and a
UPROPERTY()reference to an object?
SeniorDebuggingOccasionalHow do you debug a crash from an invalid UObject reference?
How do you debug a crash from an invalid UObject reference?
Read the callstack to find the dereferenced pointer and check whether it is a UPROPERTY(). A non-reflected pointer dangling after GC is the usual cause: the collector neither keeps the object alive nor nulls the pointer, so if (ptr) still passes on freed memory. Fix it by adding UPROPERTY() or using TWeakObjectPtr with an IsValid guard.
Common mistakes
- ✗Removing
UPROPERTY()to 'stop the GC nulling' — that causes the dangle - ✗Assuming the crash is build-config specific rather than a real lifetime bug
- ✗Forcing
CollectGarbage()instead of fixing the missing reference
Follow-up questions
- →How would
gc.PendingKillEnabledor stomp allocator help isolate the crash? - →Why does adding
UPROPERTY()change a crash into a safe null dereference you can guard?
SeniorTheoryOccasionalWhat is the cost of garbage collection and how does it affect frame time?
What is the cost of garbage collection and how does it affect frame time?
A GC pass traverses the whole reachable object graph, so cost scales with UObject count. A large graph can cause a visible frame hitch. UE mitigates this with incremental and clustered GC and tunable timing cvars.
Common mistakes
- ✗Thinking GC cost is constant rather than scaling with
UObjectcount - ✗Believing GC runs fully off the game thread and never hitches
- ✗Assuming a pass that frees nothing is free
Follow-up questions
- →How does clustering reduce the number of objects the GC must individually trace?
- →What design choices keep the
UObjectcount low in a large open world?