Unexpected orphan Nodes in Godot

18. June 2025

The more I work with Godot all day, the more edge cases I face. Recently I found out that my game had orphan Nodes that can cause memory leaks. It was not a huge deal, but still with some easy tricks, I could have noticed this much earlier.

Note: Godot version 4.4.1 is used here, some things might be different in other versions.

Find orphan nodes

Godot has already an awesome and easy way to detect orphan Nodes. You can see the amount over time by enabling the check-box at Debug > Monitors > Object > Orphan Nodes. Or you can simply call print_orphan_nodes in your scripts at some point and it will print them into the console, if some exist.

func _ready() -> void:
    print_orphan_nodes()

You will see an id, Node type and path of your orphan Nodes. If nothing gets printed, you are fine!
Here is an example with an orphan BoxContainer.

142639892262 - Stray Node: @BoxContainer@6 (Type: BoxContainer)

Normally you probably removed a Node from the tree without freeing it with queue_free.

One simple mistake is enough

In my case something else happened and I just got this cryptic prints to the console.

122456901315 - Stray Node:  (Type: Node)
122591119050 - Stray Node:  (Type: Node)
130929395586 - Stray Node:  (Type: Node)

There is no real information that could lead me to the problematic Node. But after some debugging I detected that my DataUtil, that loads and saves data is the issue.

Everywhere I created a DataUtil instance, an orphan Node would appear after changing scenes.

var data_util: DataUtil = DataUtil.new()

But why?? This looks good to me.

After some more detective work, I found out that the problem was so simple, yet so evil.

class_name DataUtil
extends Node # JUST THIS LINE

By simply removing the line extends Node, the issue vanished.

The extends Node makes the DataUtil a Node, of course. And because the Node is created, but not added to the tree, it will result as an orphan Node.

Nodes need to be released manually if not attached to a scene. RefCounted objects instead get their memory released automatically if no longer used.

Now my DataUtil extends RefCounted, the default class in Godot. This can be proved by simply creating an instance and calling get_class

class_name NoExtends

func _init() -> void:
    print(get_class()) # prints RefCounted

Anyway it's good practice to add the explicit extends RefCounted anyway. So anybody can see what class it extends without having to know what the default is.

class_name WithExtends
extends RefCounted

func _init() -> void:
    print(get_class()) # still prints RefCounted

I have to say that this DataUtil was once an auto-loaded Singleton. For that reason it extended Node in the first place. When converting it to a local instance, I forgot to remove the extends Node. But I didn't expect that this simple mistake could cause memory leaks.

Keep orphan Nodes away

I will now keep the print_orphan_nodes call in my (custom) change scene method. This should prevent future mistakes and let me detect them much earlier. It's always good to know how to detect potentially critical issues with a few simple tricks.

Last but not least a precious Godot docs entry titled: When and how to avoid using nodes for everything. This should clear some doubts about when to use Nodes and when to use something else.

Every feedback is welcome

Feel free to write me an email at info@simondalvai.org and comment on Mastodon.

mastodon button Codeberg button Email button RSS button