Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Game Development with Godot & GDScript  Module 4 – The scene tree, nodes and signals

The scene tree and node lifecycle

See the order of _init, _enter_tree, _ready, _process and _exit_tree in Godot 4.7, add and remove nodes at run time, and delete them safely with queue_free.

  • Beginner
  • 20 minutes
  • Examples run with godot 4.7.2-stable and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Predict the order of _init, _enter_tree, _ready, _process and _exit_tree in a tree of nodes
  • Add, remove and re-add nodes at run time with add_child, remove_child and request_ready
  • Delete nodes with queue_free or free, and guard old references with is_instance_valid
  • Clean up with _notification and NOTIFICATION_PREDELETE, and find orphan nodes that leak

Before you start

What you’ll learn · Before you start

What you will learn

  • Predict the order of _init, _enter_tree, _ready, _process and _exit_tree in a tree of nodes
  • Add, remove and re-add nodes at run time with add_child, remove_child and request_ready
  • Delete nodes with queue_free or free, and guard old references with is_instance_valid
  • Clean up with _notification and NOTIFICATION_PREDELETE, and find orphan nodes that leak

Before you start

On this page

In one frame of a five-node scene, Godot called its scripts 25 times: five _init(), five _enter_tree(), five _ready(), five _process() and five _exit_tree(). Their order decides what each script can count on, and it is not one order: _enter_tree() went down from the root, while _ready() came back up from the deepest node.

This page follows nodes through their whole life, from being made to being deleted. You will add and remove nodes while the game runs, see why queue_free() waits for the end of the frame, catch the error that a deleted node leaves behind, hear the notification a node gets as it is deleted, and find nodes that leak because nothing deleted them.

Nodes enter the tree from the top and get ready from the bottom

lifecycle.tscn is a small level: Level, with the children Player and HUD, a Sword under Player and a Score under HUD. The same script, lifecycle_log.gd, is attached to all five nodes and prints each callback with the node’s name:

lifecycle_log.gd: prints every callback of its node GD · lifecycle_log.gd
extends Node

# Attached to every node of lifecycle.tscn: prints each callback Godot makes, with the node's name.


func _init() -> void:
	print("_init        name '", name, "', parent ", get_parent())


func _enter_tree() -> void:
	print("_enter_tree  ", name)


func _ready() -> void:
	print("_ready       ", name)


func _process(_delta: float) -> void:
	print("_process     ", name)


func _exit_tree() -> void:
	print("_exit_tree   ", name)
One frame of the five-node scene, Godot 4.7.2 Python · lifecycle_session.py
from session import files, run

files("project.godot", "lifecycle.tscn", "lifecycle_log.gd")
# One frame of the five-node scene; then the run ends, and the scene leaves the tree.
run("""
godot --headless --no-header --fixed-fps 60 --quit-after 1 lifecycle.tscn
""")

Output

$ godot --headless --no-header --fixed-fps 60 --quit-after 1 lifecycle.tscn
_init        name '', parent <Object#null>
_init        name '', parent <Object#null>
_init        name '', parent <Object#null>
_init        name '', parent <Object#null>
_init        name '', parent <Object#null>
_enter_tree  Level
_enter_tree  Player
_enter_tree  Sword
_enter_tree  HUD
_enter_tree  Score
_ready       Sword
_ready       Player
_ready       Score
_ready       HUD
_ready       Level
_process     Level
_process     Player
_process     Sword
_process     HUD
_process     Score
_exit_tree   Score
_exit_tree   HUD
_exit_tree   Sword
_exit_tree   Player
_exit_tree   Level

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lifecycle_session.py

Read the output in blocks of five. _init() runs while the scene builds each node, before the node has a name or a parent, so it printed an empty name five times. Then the nodes enter the scene tree: The one tree of nodes of a running game, with a Window named root at the top. Every node in it has a path from the top, such as /root/Player/Camera. More in “Nodes, scenes and instancing”, and the two callbacks that follow go in opposite directions. Step through the run and watch the sub-labels collect each node’s place:

Interactive · step by step Five nodes and the order of their callbacks Step through the recorded run of lifecycle.tscn. The sub-label of each node keeps the place it got in each callback.
Five nodes and the order of their callbacks, step 10 of 10. Showing Level (exit 5th), Player (exit 4th), HUD (exit 2nd), Sword (exit 3rd), Score (exit 1st).Levelexit 5thPlayerexit 4thHUDexit 2ndSwordexit 3rdScoreexit 1st

The drawing shows the whole scene; its 10 steps are listed below.

Every step as text
  1. Five nodes are made. Godot reads lifecycle.tscn and makes its five nodes. Each one's _init() runs while it is being made, before it has a name or a parent: the run printed name '' and parent <Object#null> five times.
  2. Level enters first. The scene's root enters the tree first, so _enter_tree() runs on Level before any node below it. Its children exist, but they are not in the tree yet.
  3. Down one branch. Next comes Player, then Player's child Sword. Godot goes all the way down one branch before it starts the next one, the order called pre-order.
  4. Then the next branch. HUD enters fourth and its child Score fifth. All five _enter_tree() calls are done, and no node is ready yet.
  5. The deepest node is ready first. _ready() runs on a node only when all its children are ready. Sword has none, so its _ready() runs first of all.
  6. Then its parent. Player's only child is ready, so Player's _ready() runs second. Inside it, Player can use Sword, which is set up.
  7. The other branch. The same happens on the other side. Score is ready third and HUD fourth.
  8. The root is ready last. Level's _ready() runs last, when every node below it is ready. That makes the root's _ready() the place for code that needs the whole scene.
  9. Every frame, in tree order. From now on _process() runs once a frame on every node, in the same order as _enter_tree(), top to bottom as the Scene dock shows them. The run printed Level, Player, Sword, HUD and Score.
  10. Leaving in reverse. When the run ends, the nodes leave the tree in the exact reverse order. _exit_tree() ran on Score, HUD, Sword, Player and finally Level, so every child leaves before its parent.

_enter_tree() runs on a parent before its children, going down one branch before the next: Level, Player, Sword, HUD, Score. This is tree order: The order in which Godot goes through the nodes of the scene tree: a parent before its children, and one branch to its end before the next, top to bottom as the Scene dock lists them. _enter_tree() and _process() follow it., the order of the Scene dock from top to bottom, and _process() follows it too in every frame. _ready() waits instead: a node is ready only when all its children are, so the deepest node of the first branch, Sword, was ready first and the root, Level, last. _exit_tree() is the mirror of _enter_tree(): when the run ended, Score left first and Level last.

Callback When it runs Order across the tree
_init() once, while the node is made, before it has a name or a parent as the nodes are made
_enter_tree() every time the node enters the tree parents first: Level, Player, Sword …
_ready() once, when the node and all its children are in the tree children first: Sword, Player, Score …
_process(delta) every frame while the node is in the tree tree order
_exit_tree() every time the node leaves the tree children first, the reverse of tree order

The order has a practical meaning. In its _ready(), a parent can use its children, because their own _ready() has already run. A child’s _ready() runs before its parent’s, so a child that reads something its parent sets in _ready() gets the value from before that setup.

Key idea

_enter_tree() goes down the tree, parents first; _ready() comes back up, children first; _process() runs in tree order every frame; _exit_tree() runs in reverse, so every child leaves before its parent.

Checkpoint

Check: The order of the lifecycle callbacks

  1. Question 1 of 2 In lifecycle.tscn, Level has the children Player and HUD, Player has Sword and HUD has Score. Whose _ready() runs first?

    Choose one answer.

    A hint

    A node is ready only when all its children are.

    Show the answer: Question 1 of 2

    Answer: Sword

    _ready() runs children first: Sword, Player, Score, HUD, Level. _enter_tree() runs the other way, from the root down: Level, Player, Sword, HUD, Score.

  2. Question 2 of 2 What did _init() print for name and parent on each of the five nodes of lifecycle.tscn?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: An empty name and no parent, because the node is still being made

    _init() is the constructor: it runs before the node has a name, a parent or a place in the tree. Code that needs any of those belongs in _enter_tree() or _ready().

Removing a node keeps it alive, and it is ready only once

A game adds nodes while it runs: a pause menu when the player presses Escape, a bullet for every shot. Called on a parent that is in the tree, add_child() puts the node in and runs its _enter_tree() and _ready() before the call returns. remove_child() is not its opposite in every way: it takes the node out and runs _exit_tree(), but it deletes nothing. pause_menu.gd opens and closes a menu in the three ways a game does:

pause_menu.gd: a menu added, removed and added again GD · pause_menu.gd
extends Node

# Opens and closes a pause menu the way a game might: add it, take it out, put it back.
const Probe := preload("res://lifecycle_log.gd")


func _ready() -> void:
	var menu: Node = Probe.new()
	menu.name = "PauseMenu"
	print("-- add_child(menu)")
	add_child(menu)
	print("-- remove_child(menu)")
	remove_child(menu)
	print("menu is valid: ", is_instance_valid(menu), ", in the tree: ", menu.is_inside_tree())
	print("-- add_child(menu) again")
	add_child(menu)
	print("-- menu.request_ready(), then remove_child() and add_child()")
	menu.request_ready()
	remove_child(menu)
	add_child(menu)
	get_tree().quit()
A pause menu's callbacks as it comes and goes, Godot 4.7.2 Python · menu_session.py
from session import files, run

files("project.godot", "pause_menu.tscn", "pause_menu.gd", "lifecycle_log.gd")
run("""
godot --headless --no-header --fixed-fps 60 pause_menu.tscn
""")

Output

$ godot --headless --no-header --fixed-fps 60 pause_menu.tscn
_init        name '', parent <Object#null>
-- add_child(menu)
_enter_tree  PauseMenu
_ready       PauseMenu
-- remove_child(menu)
_exit_tree   PauseMenu
menu is valid: true, in the tree: false
-- add_child(menu) again
_enter_tree  PauseMenu
-- menu.request_ready(), then remove_child() and add_child()
_exit_tree   PauseMenu
_enter_tree  PauseMenu
_ready       PauseMenu
_process     PauseMenu
_exit_tree   PauseMenu

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 menu_session.py

Follow the same run as the life of one node. The branch at the side is the way back into the tree:

Interactive · stage by stage The life of one node, as pause_menu.gd ran it Step through the run. The sub-label of each stage says what happened to PauseMenu there.
The life of one node, as pause_menu.gd ran it, step 7 of 7. Showing Made with new(), add_child() (_enter_tree() and _ready()), In the tree, remove_child() (out, but still valid), Deleted. In focus: Deleted.add_child() againMade with new()_init() runs, no tree yetadd_child()_enter_tree() and _ready()In the tree_process() every frameremove_child()out, but still validDeletedqueue_free() or free()

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Made, not in the tree. Probe.new() makes the PauseMenu node and runs its _init(). The node exists, but it is in no tree, so Godot calls nothing else on it yet.
  2. Added and made ready. add_child(menu) puts it in the tree before the call returns. _enter_tree() runs, then _ready(), because the menu has no children to wait for.
  3. Taken out, not deleted. remove_child(menu) takes it out of the tree, and _exit_tree() runs. The node is not deleted. The run printed menu is valid true, in the tree false.
  4. Added again. add_child(menu) again runs _enter_tree() a second time but not _ready(). A node becomes ready once in its life.
  5. Ready on request. menu.request_ready() asks for one more _ready(). It runs at the next entry, so after one more remove_child() and add_child() the run printed _ready PauseMenu again.
  6. Processed every frame. While the menu is in the tree, _process() runs on it once a frame. One frame passed before this run ended, and the run printed _process PauseMenu once.
  7. Deleted at the end. Only freeing a node deletes it. Here the run ended, and Godot freed the whole tree, so the menu left it one last time with _exit_tree().

Three facts come out of the run. After remove_child() the menu was still valid and simply out of the tree; a node outside the tree is an orphan node: A node that exists but is not in the scene tree, such as one made with new() and never added, or one taken out with remove_child(). It gets no callbacks, and it stays in memory until something frees it., and it stays in memory until something adds it back or deletes it. Added again, it ran _enter_tree() but not _ready(): the Node class reference says _ready() may be called only once for each node. And request_ready() asks for one more _ready(), which runs the next time the node enters the tree, as the last add_child() showed. A menu that builds its buttons in _ready() and is reopened by adding it back would therefore show its old buttons, unless it asks with request_ready().

Setting up again in _ready()

A pickup that resets its value in _ready() and is reused by taking it out and adding it back keeps its old value, because _ready() does not run again. Put the reset in _enter_tree(), which runs on every entry, or call request_ready() before the pickup goes back in.

Checkpoint

Check: Adding, removing and re-adding nodes

  1. One question A pause menu was added once, then taken out with remove_child(). The game adds it again with add_child(). Which of its callbacks run now?

    Choose one answer.

    Show the answer: One question

    Answer: _enter_tree() only

    remove_child() keeps the node alive and runs _exit_tree(). Adding it again runs _enter_tree() again; _ready() runs again only after request_ready().

A queued node lives until the end of the frame

Bullets show the other half of a node’s life. gun.gd fires one bullet a frame for four frames, adding each as a child of the gun, and every bullet deletes itself with queue_free() in its second frame:

gun.gd: one bullet a frame GD · gun.gd
extends Node

# Fires one bullet a frame for four frames. Each bullet is added as a child of the gun.
const Bullet := preload("res://bullet.gd")
var frame := 0


func _process(_delta: float) -> void:
	frame += 1
	if frame <= 4:
		var bullet := Bullet.new()
		bullet.name = "Bullet%d" % frame
		add_child(bullet)
		print("frame ", frame, ": fired ", bullet.name, "; children of the gun: ", get_child_count())
	else:
		print("frame ", frame, ": children of the gun: ", get_child_count())
bullet.gd: deletes itself in its second frame GD · bullet.gd
extends Node

# A bullet that deletes itself in its second frame.
var age := 0


func _process(_delta: float) -> void:
	age += 1
	print("  ", name, " _process, age ", age)
	if age == 2:
		queue_free()
		print("  ", name, " called queue_free(), and is still in the tree: ", is_inside_tree())


func _exit_tree() -> void:
	print("  ", name, " leaves the tree")

freeing.gd then deletes two nodes the two ways Godot offers, and makes the mistake that follows from the second one:

freeing.gd: queue_free() against free(), and a stale variable GD · freeing.gd
extends Node

# Deletes one node with queue_free() and one with free(), then looks at what is left of them.
var coin: Node2D
var gem: Node2D
var frame := 0


func _ready() -> void:
	coin = Node2D.new()
	coin.name = "Coin"
	add_child(coin)
	coin.queue_free()
	print("Coin after queue_free(): valid ", is_instance_valid(coin), ", in the tree ",
			coin.is_inside_tree(), ", queued ", coin.is_queued_for_deletion())
	gem = Node2D.new()
	gem.name = "Gem"
	add_child(gem)
	gem.free()
	print("Gem after free(): valid ", is_instance_valid(gem), ", equal to null ", gem == null,
			", prints as ", gem)


func _process(_delta: float) -> void:
	frame += 1
	print("frame ", frame, ": Coin valid ", is_instance_valid(coin), ", children ", get_child_count())
	if frame == 2:
		get_tree().quit()
		# The mistake: using a variable that still refers to a node that was freed.
		print("Gem's name: ", gem.name)
Bullets that free themselves, and a node used after free(), Godot 4.7.2 Python · freeing_session.py
from session import files, run

files("project.godot", "gun.tscn", "gun.gd", "bullet.gd", "freeing.tscn", "freeing.gd")
# Six frames of the gun, then the two ways of deleting a node; the second run ends with a script error on purpose.
run("""
godot --headless --no-header --fixed-fps 60 --quit-after 6 gun.tscn
!godot --headless --no-header --fixed-fps 60 freeing.tscn
""")

Output

$ godot --headless --no-header --fixed-fps 60 --quit-after 6 gun.tscn
frame 1: fired Bullet1; children of the gun: 1
frame 2: fired Bullet2; children of the gun: 2
  Bullet1 _process, age 1
frame 3: fired Bullet3; children of the gun: 3
  Bullet1 _process, age 2
  Bullet1 called queue_free(), and is still in the tree: true
  Bullet2 _process, age 1
  Bullet1 leaves the tree
frame 4: fired Bullet4; children of the gun: 3
  Bullet2 _process, age 2
  Bullet2 called queue_free(), and is still in the tree: true
  Bullet3 _process, age 1
  Bullet2 leaves the tree
frame 5: children of the gun: 2
  Bullet3 _process, age 2
  Bullet3 called queue_free(), and is still in the tree: true
  Bullet4 _process, age 1
  Bullet3 leaves the tree
frame 6: children of the gun: 1
  Bullet4 _process, age 2
  Bullet4 called queue_free(), and is still in the tree: true
  Bullet4 leaves the tree
$ godot --headless --no-header --fixed-fps 60 freeing.tscn
Coin after queue_free(): valid true, in the tree true, queued true
Gem after free(): valid false, equal to null true, prints as <Freed Object>
frame 1: Coin valid false, children 0
frame 2: Coin valid false, children 0
SCRIPT ERROR: Invalid access to property or key 'name' on a base object of type 'previously freed'.
          at: _process (res://freeing.gd:30)
          GDScript backtrace (most recent call first):
              [0] _process (res://freeing.gd:30)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 freeing_session.py

In the gun’s run, Bullet1 called queue_free() in frame 3 and was still in the tree on the next line; it left the tree only after every other node had processed that frame. A bullet added during frame 1 also got its first _process() in frame 2, not straight away. queue_free() only marks the node, and Godot deletes it at the end of the current frame. Until then it is valid, it is still a child, and is_queued_for_deletion() says it is going. That makes it the safe choice: any code that still holds the node this frame keeps working.

free() deletes at once. Gem was invalid on the very next line, yet the variable gem was not cleared: it still held the deleted object, which prints as <Freed Object>, and reading gem.name two frames later stopped _process() with a “previously freed” error. Godot 4.7.2 counts such a variable as equal to null, as the run printed, but the Object class reference says not to test for a deleted object by comparing it with null: use is_instance_valid(), which asks whether the object still exists.

Call What happens to the node Use it for
queue_free() deleted at the end of the frame, after the deferred calls; valid until then deleting a node in a game, the usual choice
free() deleted at once; every reference to it is “previously freed” a node made and dropped in the same function
remove_child() taken out of the tree, not deleted: an orphan, still valid keeping a node to add again later

Checkpoint

Check: queue_free(), free() and old references

  1. One question Right after coin.queue_free(), the next line prints is_instance_valid(coin). What does it print, and when does that change?

    Choose one answer.

    Show the answer: One question

    Answer: true; the coin is deleted at the end of the frame, and is_instance_valid(coin) is false in the next one

    queue_free() deletes the node at the end of the current frame, after everything else in the frame has run. Until then it is valid and still in the tree; is_queued_for_deletion() tells you it is going.

Notifications tell a node everything, up to its deletion

The callbacks are the common cases of something more general. Godot sends every object notification: A numbered message Godot sends to an object, such as NOTIFICATION_READY or NOTIFICATION_PREDELETE. A script hears every one in _notification(what); callbacks such as _ready() are notifications with a function of their own., numbered messages such as NOTIFICATION_READY, and a script hears all of them in one function, _notification(what). chest.gd prints the ones it knows by name while notify.gd takes it through its life. Then a RefCounted object, save_handle.gd, loses its last reference:

chest.gd: names the notifications it receives GD · chest.gd
extends Node

# Prints each notification in NAMES that it receives. Godot sends a few more, which this script skips.
const NAMES := {
	NOTIFICATION_PATH_RENAMED: "PATH_RENAMED",
	NOTIFICATION_PARENTED: "PARENTED",
	NOTIFICATION_ENTER_TREE: "ENTER_TREE",
	NOTIFICATION_POST_ENTER_TREE: "POST_ENTER_TREE",
	NOTIFICATION_READY: "READY",
	NOTIFICATION_EXIT_TREE: "EXIT_TREE",
	NOTIFICATION_UNPARENTED: "UNPARENTED",
	NOTIFICATION_PREDELETE: "PREDELETE",
}


func _notification(what: int) -> void:
	if NAMES.has(what):
		print("  ", name, " got NOTIFICATION_", NAMES[what])
save_handle.gd: a RefCounted object that hears its own deletion GD · save_handle.gd
extends RefCounted

# Not a node: Godot deletes it by itself as soon as nothing refers to it any more.


func _notification(what: int) -> void:
	if what == NOTIFICATION_PREDELETE:
		print("  SaveHandle got NOTIFICATION_PREDELETE: the place to close its file")
notify.gd: a chest from new() to free(), then a RefCounted object GD · notify.gd
extends Node

# Takes a chest through its whole life, then drops the last reference to a RefCounted object.
const Chest := preload("res://chest.gd")
const SaveHandle := preload("res://save_handle.gd")


func _ready() -> void:
	print("-- new(), then name = \"Chest\"")
	var chest: Node = Chest.new()
	chest.name = "Chest"
	print("-- add_child(chest)")
	add_child(chest)
	print("-- remove_child(chest)")
	remove_child(chest)
	print("-- chest.free()")
	chest.free()
	print("-- handle = null")
	var handle: RefCounted = SaveHandle.new()
	handle = null
	print("-- the end of _ready()")
	get_tree().quit()

end_of_frame.gd shows the end of a frame in which a node with children is queued for deletion. Player, Sword, Shield and HUD carry goodbye_log.gd, which prints only _exit_tree() and NOTIFICATION_PREDELETE:

end_of_frame.gd: Player queued, then a deferred call GD · end_of_frame.gd
extends Node

# In frame 1, queues Player for deletion and asks for a deferred call; in frame 2, lists what is left.
var frame := 0


func _process(_delta: float) -> void:
	frame += 1
	if frame == 1:
		print("frame 1, _process: queue_free() on Player, then call_deferred()")
		$Player.queue_free()
		report.call_deferred()
		print("frame 1, _process ends")
	elif frame == 2:
		var names: Array[String] = []
		for child in get_children():
			names.append(child.name)
		print("frame 2: children of Level: ", names)
		get_tree().quit()


func report() -> void:
	print("deferred call: Player is still valid: ", is_instance_valid($Player))
Notifications of a chest, and the end of a frame with a deletion, Godot 4.7.2 Python · notify_session.py
from session import files, run

files("project.godot", "notify.tscn", "notify.gd", "chest.gd", "save_handle.gd",
      "end_of_frame.tscn", "end_of_frame.gd", "goodbye_log.gd")
run("""
godot --headless --no-header --fixed-fps 60 notify.tscn
godot --headless --no-header --fixed-fps 60 end_of_frame.tscn
""")

Output

$ godot --headless --no-header --fixed-fps 60 notify.tscn
-- new(), then name = "Chest"
  Chest got NOTIFICATION_PATH_RENAMED
-- add_child(chest)
  Chest got NOTIFICATION_PARENTED
  Chest got NOTIFICATION_ENTER_TREE
  Chest got NOTIFICATION_POST_ENTER_TREE
  Chest got NOTIFICATION_READY
-- remove_child(chest)
  Chest got NOTIFICATION_EXIT_TREE
  Chest got NOTIFICATION_UNPARENTED
-- chest.free()
  Chest got NOTIFICATION_PREDELETE
-- handle = null
  SaveHandle got NOTIFICATION_PREDELETE: the place to close its file
-- the end of _ready()
$ godot --headless --no-header --fixed-fps 60 end_of_frame.tscn
frame 1, _process: queue_free() on Player, then call_deferred()
frame 1, _process ends
deferred call: Player is still valid: true
  PREDELETE   Player
  _exit_tree  Shield
  _exit_tree  Sword
  _exit_tree  Player
  PREDELETE   Shield
  PREDELETE   Sword
frame 2: children of Level: ["HUD"]
  _exit_tree  HUD
  PREDELETE   HUD

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 notify_session.py

The chest heard more than the callbacks: PARENTED before it entered and UNPARENTED after it left, and PREDELETE when free() deleted it. NOTIFICATION_PREDELETE comes when an object is about to be deleted, and it has no callback of its own, so _notification() is the only way to act on it. The SaveHandle heard it the moment handle = null dropped the only reference: a An object that counts the variables and other objects that refer to it, and deletes itself as soon as the count reaches zero. Resources work this way; nodes do not, so a node must be freed. object deletes itself, while a node waits until something frees it.

The second run is a frame with a deletion in it. Step through what happened to Player:

Interactive · stage by stage What happens to Player at the end of frame 1 Step through frame 1 of end_of_frame.tscn. The sub-label of each stage says what it did in that frame.
What happens to Player at the end of frame 1, step 6 of 6. Showing _process(), tree order, Deferred calls (report(): Player valid), Queued deletions (PREDELETE on Shield, Sword), The next frame. In focus: The next frame._process(), tree orderLevel marks PlayerDeferred callsreport(): Player validQueued deletionsPREDELETE on Shield, SwordThe next framechildren of Level: HUD

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Player is marked. In frame 1, Level's _process() calls $Player.queue_free() and report.call_deferred(). Neither happens yet; the rest of _process() runs, and the run printed "frame 1, _process ends".
  2. Deferred calls come first. When processing is over, Godot runs the calls that were deferred. report() finds Player still valid, because a queued node is deleted only after every deferred call.
  3. PREDELETE comes first. Now Player is deleted. It receives NOTIFICATION_PREDELETE first, while it is still in the tree with its children.
  4. The subtree leaves. Deleting a node takes it out of the tree, with everything under it: _exit_tree() runs on Shield, Sword and Player, children first.
  5. The children are deleted. Deleting a parent deletes its children too. Shield and Sword receive their PREDELETE last, after they have left the tree.
  6. The next frame. In frame 2, Level has one child left, HUD: the run printed children of Level: ["HUD"]. Then the game quits, and HUD goes the other way round: it leaves the tree first and gets its PREDELETE after.

report(), a deferred call: A call made with call_deferred() or a CONNECT_DEFERRED connection. It runs at idle time, after the frame's _process() calls and before its timers and queued deletions, instead of at once. More in “await, coroutines and timers”, still found Player valid, because a queued node goes after every deferred call. Then Player received PREDELETE while it was still in the tree, its subtree left the tree children first, and only then were Sword and Shield deleted. When the game quits, the order is the other way round, as the run’s last two lines show: HUD left the tree with the rest of the game, and only then was it deleted.

Undo in _exit_tree(), close in PREDELETE

Work that a node does on entering, such as joining a list of targets, is undone in _exit_tree(), which runs every time the node leaves, deleted or not. Keep NOTIFICATION_PREDELETE for what must happen once, when the object is deleted, such as closing a file it holds open.

Checkpoint

Check: Notifications and PREDELETE

  1. One question Player, with the children Sword and Shield, is deleted with queue_free() while it is in the tree. Which of these lines was printed first?

    Choose one answer.

    Show the answer: One question

    Answer: PREDELETE Player

    The run printed PREDELETE Player, then _exit_tree on Shield, Sword and Player, then PREDELETE on Shield and Sword. A node quitting with the game leaves the tree first instead, as HUD did at the end of the run, so cleanup that must always happen on leaving goes in _exit_tree().

A node nobody deletes is an orphan, and it leaks

despawn.gd spawns an enemy every frame and keeps two: each frame, the oldest one goes. One scene takes it out with remove_child(), the other with queue_free(). Every frame prints the enemies in the tree and the orphan count from Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT); in frame 5, the first scene also calls Node.print_orphan_nodes():

despawn.gd: the oldest enemy goes, one way or the other GD · despawn.gd
extends Node

# Spawns an enemy every frame and keeps two: the oldest one goes, with remove_child() or with queue_free().
@export var use_queue_free := false
var frame := 0


func _process(_delta: float) -> void:
	frame += 1
	var enemy := Node2D.new()
	enemy.name = "Enemy%d" % frame
	add_child(enemy)
	if get_child_count() > 2:
		var oldest := get_child(0)
		if use_queue_free:
			oldest.queue_free()
		else:
			remove_child(oldest)
	print("frame ", frame, ": enemies in the tree ", get_child_count(), ", orphan nodes ",
			Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT))
	if frame == 5 and not use_queue_free:
		Node.print_orphan_nodes()
Five frames with remove_child(), then five with queue_free(), Godot 4.7.2 Python · orphans_session.py
from session import files, run

files("project.godot", "despawn.gd", "despawn.tscn", "despawn_fixed.tscn")
# Five frames with remove_child(), then five with queue_free(). print_orphan_nodes() starts each line with an object
# id that is new on every run, shown here as <id>.
run("""
godot --headless --no-header --fixed-fps 60 --quit-after 5 despawn.tscn
godot --headless --no-header --fixed-fps 60 --quit-after 5 despawn_fixed.tscn
""", mask=[(r"(?m)^\d+ - Stray Node", "<id> - Stray Node")])

Output

$ godot --headless --no-header --fixed-fps 60 --quit-after 5 despawn.tscn
frame 1: enemies in the tree 1, orphan nodes 0.0
frame 2: enemies in the tree 2, orphan nodes 0.0
frame 3: enemies in the tree 2, orphan nodes 1.0
frame 4: enemies in the tree 2, orphan nodes 2.0
frame 5: enemies in the tree 2, orphan nodes 3.0
<id> - Stray Node: Enemy2 (Type: Node2D) (Source:)
<id> - Stray Node: Enemy3 (Type: Node2D) (Source:)
<id> - Stray Node: Enemy1 (Type: Node2D) (Source:)
WARNING: 3 RIDs of type "CanvasItem" were leaked.
   at: _free_rids (servers/rendering/renderer_canvas_cull.cpp:2735)
WARNING: 3 ObjectDB instances were leaked at exit (run with `--verbose` for details).
   at: cleanup (core/object/object.cpp:2536)
$ godot --headless --no-header --fixed-fps 60 --quit-after 5 despawn_fixed.tscn
frame 1: enemies in the tree 1, orphan nodes 0.0
frame 2: enemies in the tree 2, orphan nodes 0.0
frame 3: enemies in the tree 3, orphan nodes 0.0
frame 4: enemies in the tree 3, orphan nodes 0.0
frame 5: enemies in the tree 3, orphan nodes 0.0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 orphans_session.py

With remove_child(), the level never held more than two enemies, so the scene tree looked tidy, but the orphan count rose by one every frame from frame 3. print_orphan_nodes() named the three nodes, and when the run ended Godot reported them twice: 3 ObjectDB instances were leaked at exit, and three leaked CanvasItem resources, the drawing resources that each Node2D enemy had reserved on the rendering server. With queue_free(), the count stayed at 0. Notice also that the second scene printed 3 enemies in the tree from frame 3: a queued enemy is still a child until the frame ends.

Both tools work only in debug builds, the kind the editor and a test run use; in a release export the monitor returns 0. So watch the orphan count while you test. A count that only goes up means nodes are being taken out, or made with new(), and never deleted.

Key idea

Only freeing deletes a node: a node taken out with remove_child() or never added is an orphan that keeps its memory until something frees it, and the orphan monitor is how you see it.

Checkpoint

Check: Orphan nodes and leaks

  1. One question despawn.tscn with remove_child() adds one enemy a frame and takes out the oldest whenever there are more than two. How many orphan nodes does the monitor show after frame 8?

    Type a number.

    A hint

    The run showed 1 after frame 3, 2 after frame 4 and 3 after frame 5.

    Show the answer: One question

    Answer: 6 nodes

    From frame 3 on, every frame takes one enemy out without deleting it, so the count is the frame number minus 2: 6 after frame 8. With queue_free() it stays at 0.

Practise: a spawner that cleans up after itself

spawner.gd adds enemies to the level it is in, its parent, so that they do not move when the spawner moves. Your task: when the spawner leaves the tree, it must free every enemy it spawned that is still alive. Some will already have been deleted by the game.

  1. Create an empty folder for the task and an empty project.godot file inside it; save the two scripts below next to that file.
  2. Write your code in place of the pass line of _exit_tree() in spawner.gd.
  3. Start the checker from a terminal in the same folder: godot --headless --no-header --script check_spawner.gd.
spawner.gd: the starter file GD · practice/spawner.gd
extends Node

# A spawner that adds enemies to the level it is in (its parent), so they do not move with it.
# Your task: when the spawner leaves the tree, free every enemy it spawned that is still alive.

var spawned: Array[Node] = []


func spawn(count: int) -> void:
	for i in count:
		var enemy := Node2D.new()
		enemy.name = "Enemy%d" % (spawned.size() + 1)
		get_parent().add_child(enemy)
		spawned.append(enemy)


func _exit_tree() -> void:
	pass # Replace this line with your code.
check_spawner.gd: the checker of this task GD · practice/check_spawner.gd
extends SceneTree

# Checks spawner.gd. Run it in the project's folder: godot --headless --no-header --script check_spawner.gd
# The checker plays four frames: it builds a level, lets two enemies die, then deletes the spawner.
var level: Node
var player: Node
var spawner: Node
var enemies: Array[Node] = []
var orphans_at_start := 0
var frame := 0
var passed := 0
var total := 0


func _process(_delta: float) -> bool:
	frame += 1
	if frame == 1:
		orphans_at_start = orphan_count()
		level = Node.new()
		level.name = "Level"
		root.add_child(level)
		player = Node2D.new()
		player.name = "Player"
		level.add_child(player)
		spawner = load("res://spawner.gd").new()
		spawner.name = "Spawner"
		level.add_child(spawner)
		spawner.spawn(5)
		for child in level.get_children():
			if child != player and child != spawner:
				enemies.append(child)
		check(enemies.size() == 5, "spawn(5) adds 5 enemies to the level (it added %d)" % enemies.size())
		print("Two enemies die in the game: %s and %s" % [enemies[1].name, enemies[3].name])
		enemies[1].queue_free()
		enemies[3].queue_free()
	elif frame == 2:
		print("The spawner is deleted with queue_free()")
		spawner.queue_free()
	elif frame == 4:
		var alive := 0
		for enemy in enemies:
			if is_instance_valid(enemy):
				alive += 1
		check(alive == 0, "no enemy of the spawner is left (%d left)" % alive)
		check(is_instance_valid(player) and player.is_inside_tree(), "the player is still in the level")
		check(orphan_count() == orphans_at_start, "no orphan nodes are left (%d at the start, %d now)" % [orphans_at_start, orphan_count()])
		level.free()
		print("%d of %d checks passed" % [passed, total])
		quit(0 if passed == total else 1)
	return false


func orphan_count() -> int:
	return int(Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT))


func check(ok: bool, what: String) -> void:
	total += 1
	passed += int(ok)
	print("%s %s" % ["PASS" if ok else "FAIL", what])

The checker builds a level with a player and the spawner, spawns five enemies, lets the game delete two of them, then deletes the spawner. This is what it printed for a correct spawner, and for a tempting one that frees every enemy in its list without looking first:

The checker on a right answer and on a tempting one, Godot 4.7.2 Python · check_session.py
from session import files, run

# The checker on a spawner.gd that solves the task, then on one that frees every enemy without checking it first.
files("practice/project.godot=right/project.godot", "practice/check_spawner.gd=right/check_spawner.gd",
      "practice/solution/spawner.gd=right/spawner.gd",
      "practice/project.godot=tempting/project.godot", "practice/check_spawner.gd=tempting/check_spawner.gd",
      "practice/tempting/spawner.gd=tempting/spawner.gd")
run("""
cd right
godot --headless --no-header --script check_spawner.gd
cd ../tempting
!godot --headless --no-header --script check_spawner.gd
""")

Output

$ cd right
$ godot --headless --no-header --script check_spawner.gd
PASS spawn(5) adds 5 enemies to the level (it added 5)
Two enemies die in the game: Enemy2 and Enemy4
The spawner is deleted with queue_free()
PASS no enemy of the spawner is left (0 left)
PASS the player is still in the level
PASS no orphan nodes are left (0 at the start, 0 now)
4 of 4 checks passed
$ cd ../tempting
$ godot --headless --no-header --script check_spawner.gd
PASS spawn(5) adds 5 enemies to the level (it added 5)
Two enemies die in the game: Enemy2 and Enemy4
The spawner is deleted with queue_free()
SCRIPT ERROR: Cannot call method 'queue_free' on a previously freed instance.
          at: _exit_tree (res://spawner.gd:19)
          GDScript backtrace (most recent call first):
              [0] _exit_tree (res://spawner.gd:19)
FAIL no enemy of the spawner is left (2 left)
PASS the player is still in the level
PASS no orphan nodes are left (0 at the start, 0 now)
3 of 4 checks passed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_session.py

The tempting answer called queue_free() on Enemy2, which the game had already deleted. The error stopped _exit_tree() at that line, so Enemy3 and Enemy5, later in the list, were never freed. A correct spawner ends with 4 of 4 checks passed. After that, the questions below go over the five ideas of this page, starting with the one you know least, and each choice says at once why it is right or wrong:

Practice that adapts to you

Practise this lesson

17 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 17 A Boss node has two children, Head and then Body, and Body has a child, Arm. Put the _enter_tree() calls in the order Godot makes them.

    Give each item its position, from 1 (first).

    Show the answer: Question 1 of 17

    Answer:

    1. Boss
    2. Head
    3. Body
    4. Arm

    _enter_tree() starts at the root and goes down one branch before the next, in the order the children were added: Boss, Head, then Body and its child Arm.

  2. Question 2 of 17 The same Boss tree: Boss with the children Head and Body, and Arm under Body. Put the _ready() calls in order.

    Give each item its position, from 1 (first).

    Show the answer: Question 2 of 17

    Answer:

    1. Head
    2. Arm
    3. Body
    4. Boss

    A node is ready when its children are. Head has none, so it goes first; Body must wait for Arm; Boss waits for both branches and comes last.

  3. Question 3 of 17 In what order does Godot call _process() on the nodes of a scene in each frame?

    Choose one answer.

    Show the answer: Question 3 of 17

    Answer: In tree order, the order of _enter_tree(), top to bottom as the Scene dock shows them

    _process() runs in tree order. A node's process_priority can change that order: lower numbers are called first.

  4. Question 4 of 17 When the run of lifecycle.tscn ends, which node's _exit_tree() runs first?

    Choose one answer.

    Show the answer: Question 4 of 17

    Answer: Score, the last node in tree order

    The run printed _exit_tree on Score, HUD, Sword, Player and Level: every child leaves before its parent.

  5. Question 5 of 17 Player sets max_health in its own _ready(). Level, its parent, reads $Player.max_health in Level's _ready(). Does Level see the value Player set?

    Choose one answer.

    Show the answer: Question 5 of 17

    Answer: Yes, because a parent's _ready() runs after the _ready() of all its children

    A parent can rely on its children in _ready(). The opposite does not hold: a child's _ready() runs before its parent's, so it sees the parent before the parent's own setup.

  6. Question 6 of 17 A node is added, removed and added again three times; request_ready() is never called. How many times does its _ready() run?

    Type a number.

    Show the answer: Question 6 of 17

    Answer: 1 times

    _ready() runs once in a node's life, on its first entry into the tree. Each later entry runs _enter_tree() only.

  7. Question 7 of 17 What does request_ready() do?

    Choose one answer.

    Show the answer: Question 7 of 17

    Answer: It makes _ready() run again the next time the node enters the tree

    request_ready() suits a node that is reused, such as a menu that should build itself again each time it opens.

  8. Question 8 of 17 In frame 1, the gun's _process() adds Bullet1 with add_child(). When does Bullet1's own _process() run for the first time?

    Choose one answer.

    Show the answer: Question 8 of 17

    Answer: In frame 2, the next frame

    add_child() runs the entry callbacks at once, and processing starts with the next frame. Code that needs the new node in the same frame can call it directly.

  9. Question 9 of 17 When does queue_free() delete a node?

    Choose one answer.

    Show the answer: Question 9 of 17

    Answer: At the end of the current frame, after all the deferred calls

    queue_free() marks the node; Godot deletes it at the end of the frame. It is safe to call it twice, and is_queued_for_deletion() tells you it has been called.

  10. Question 10 of 17 A turret keeps the enemy it aims at in target. The enemy may have been deleted since the last frame. Which line belongs at the top of the turret's _process()?

    Read the code, then choose one answer.

    var target: Node2D
    
    func _process(delta: float) -> void:
    	# which line?
    	look_at(target.global_position)
    Show the answer: Question 10 of 17

    Answer: if not is_instance_valid(target): return

    A reference to a deleted node becomes "previously freed", and using it stops the function with an error. Check it with is_instance_valid(), or connect to the enemy's tree_exiting signal and clear target there.

  11. Question 11 of 17 An enemy dies inside its own hit() method, which the bullet that hit it called. Which call should hit() use to delete the enemy?

    Choose one answer.

    Show the answer: Question 11 of 17

    Answer: queue_free(), so the bullet's code and the rest of the frame can still use the enemy safely

    queue_free() keeps the node usable until the end of the frame, while free() makes every reference to it invalid at once, so queue_free() is the safe choice whenever other code may still use the node in this frame. free() suits a node made and dropped in the same function.

  12. Question 12 of 17 What is NOTIFICATION_PREDELETE?

    Choose one answer.

    Show the answer: Question 12 of 17

    Answer: The notification an object gets just before Godot deletes it

    A script hears it in _notification(what) with what == NOTIFICATION_PREDELETE. There is no callback of its own.

  13. Question 13 of 17 handle holds the only reference to a SaveHandle object, which extends RefCounted. When does it receive NOTIFICATION_PREDELETE after handle = null?

    Choose one answer.

    Show the answer: Question 13 of 17

    Answer: At once, on that line

    Resources and objects of scripts that extend RefCounted count their references and go when the count reaches zero. Nodes do not count references, so they must be freed.

  14. Question 14 of 17 Each enemy adds itself to a radar's list when it enters the tree. Enemies can also be taken out with remove_child() and kept for later. Where should an enemy take itself off the list?

    Choose one answer.

    Show the answer: Question 14 of 17

    Answer: In _exit_tree(), which runs whenever it leaves the tree

    Pair work that happens on entry with work on exit: add in _enter_tree() or _ready(), take off in _exit_tree(). Keep PREDELETE for what must happen once when the object is deleted, such as closing a file.

  15. Question 15 of 17 What is an orphan node?

    Choose one answer.

    Show the answer: Question 15 of 17

    Answer: A node that exists but is not in the scene tree

    An orphan gets no callbacks and draws nothing. It is a leak unless something frees it or adds it back.

  16. Question 16 of 17 A test run ends with "WARNING: 3 ObjectDB instances were leaked at exit". What does it tell you?

    Choose one answer.

    Show the answer: Question 16 of 17

    Answer: Three objects were still alive when Godot shut down, because nothing freed them

    The warning counts objects nobody freed. During the game, the orphan monitor and Node.print_orphan_nodes() show which nodes they are.

  17. Question 17 of 17 Memory use grows during long play sessions of your game. Which check tells you whether nodes are leaking, and which ones?

    Choose one answer.

    Show the answer: Question 17 of 17

    Answer: Watch Performance.OBJECT_ORPHAN_NODE_COUNT, then call Node.print_orphan_nodes() to list them

    A leak of nodes shows as an orphan count that keeps rising. Both work only in debug builds, the kind the editor and a test run use; in a release export the monitor returns 0.

Interview questions

Warm-up: what is the difference between _enter_tree() and _ready() in Godot?

_enter_tree() runs every time a node enters the scene tree, and it runs on a parent before its children, so when it runs the children are not in the tree yet. _ready() runs once in a node’s life, on its first entry, and only after all its children have entered and run their own _ready(). In a five-node scene on Godot 4.7.2 the enter calls came as Level, Player, Sword, HUD, Score and the ready calls as Sword, Player, Score, HUD, Level. So _ready() is the place for setup that needs the children, such as reading $Sword. A node taken out with remove_child() and added again gets _enter_tree() again but not _ready(), unless request_ready() was called. _exit_tree() is the counterpart of _enter_tree(), and it runs children first.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    During long boss fights a shooter's memory use climbs and never comes back, yet the scene tree never holds more than 40 bullets. Bullets that fly off the screen run get_parent().remove_child(self). What is going on, how do you prove it, and what do you change?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    A Level script counts its enemies in _ready() and stores the number in enemy_count. The HUD, a child of Level, shows level.enemy_count in its own _ready() and always shows 0, though the level has 12 enemies. Explain the order of events and give two ways to fix it.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • _init() runs while a node is made, before it has a name or a parent; _enter_tree() then runs parents first, _ready() children first, _process() in tree order every frame, and _exit_tree() in reverse tree order.
  • add_child() runs _enter_tree() and _ready() before it returns, remove_child() takes a node out without deleting it, and _ready() runs once in a node’s life unless request_ready() asks for another.
  • queue_free() deletes a node at the end of the frame, after the deferred calls, so it stays valid until then; free() deletes at once, and is_instance_valid() is the test for a reference that may be stale.
  • _notification(what) hears every notification, and NOTIFICATION_PREDELETE comes just before an object is deleted; a RefCounted object deletes itself when its last reference goes, while a node must be freed.
  • A node outside the tree that nothing frees is an orphan and leaks; the orphan monitor and print_orphan_nodes() find it in debug builds.

Terms in this lesson

notification
A numbered message Godot sends to an object, such as NOTIFICATION_READY or NOTIFICATION_PREDELETE. A script hears every one in _notification(what); callbacks such as _ready() are notifications with a function of their own.
orphan node
A node that exists but is not in the scene tree, such as one made with new() and never added, or one taken out with remove_child(). It gets no callbacks, and it stays in memory until something frees it.
tree order
The order in which Godot goes through the nodes of the scene tree: a parent before its children, and one branch to its end before the next, top to bottom as the Scene dock lists them. _enter_tree() and _process() follow it.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.