When an gameobject/entity is destroyed during a locked phase(physics step, contact callback, iteration over a system’s array), the engine doesn’t tear it down on the spot. It flags the entity, removes it from simulation at the end of the step, and reclaims or frees the memory later still often by recycling the slot rather than freeing it at all.
how different engines does this differently
Unreal Engine 5
use AActor::Destroy() for actors, DestroyComponent() for components, and for plain UObjects just null out the references; MarkAsGarbage() now only tracks stale objects and no longer forces collection.
What actually frees memory is reachability, an object survives until every strong reference is released, then gets collected on some later GC pass (staged BeginDestroy → FinishDestroy, possibly across frames).
Unity (GameObject path)
Flush window. Actual destruction is always delayed until after the current Update loop, but always happens before rendering; if the object is a component, only that component is removed.
→ Object.Destroy, Unity Scripting Reference: https://docs.unity3d.com/ScriptReference/Object.Destroy.html
Two edge cases in the same page. Once Destroy(obj, t) is scheduled it fires after t seconds regardless of whether the calling script component is later disabled or destroyed, and if Time.timeScale is 0 the destruction waits until time resumes.
→ same URL as above.
Unity (DOTS/Entity)
https://docs.unity3d.com/Packages/com.unity.entities%401.4/manual/performance-sync-points.html
Why deferral, and it isn’t dangling pointers. Structural changes are the primary cause of sync points, and a sync point blocks the main thread until all scheduled jobs complete. Crucially: structural changes also invalidate all direct references to component data, including DynamicBuffer instances.
Above Unity doc teel that we need to use entity command buffer to make structural changes like deleting, adding and adding components etc. This combines all the structural changes into one, improving performance.
Godot
QueueFree queues a node for deletion at the end of the current frame along with all its children, and is the safe counterpart to Free; IsQueuedForDeletion reports whether a node will be deleted at frame end. The reference-safety warning is on the same page: a variable pointing at a freed node is not nulled — it points at a previously freed instance, and must be validated with is_instance_valid before use.
→ Node.QueueFree API: https://cyoann.github.io/GodotSharpAPI/html/13981912-ab2f-a208-65da-539225d73a08.htm (community mirror of the official C# API docs) GitHubGitHub
Why deferral exists here. The engine docs quoted in this thread state that deleting a node currently “blocked” while emitting a signal or calling a function will crash the game, and that queue_free is the safe way, erasing during idle.
→ Godot Q&A: https://godotengine.org/qa/4062/freeing-objects-nodes?show=4108 (forum, quoting the official docs — secondary) godotengine
The practical footgun, from a developer post-mortem rather than docs: a previously freed instance doesn’t evaluate false in an if target: check, so code calls methods on it, producing errors in debug and crashes in release.
Source Engine
https://developer.valvesoftware.com/wiki/UTIL_Remove
Marks an object for deletion on the next frame, instead of the current one. Removing objects on the current frame can cause null pointers which will cause the game to crash