Skip to content

Why do I get "previously freed instance"?

Godot 4.x

You’ll see something like this in the Debugger panel. The function name will be your own:

_on_timer_timeout(): Attempt to call function 'take_damage' in base 'previously freed' on a null instance.
What’s happeningGo to
The error comes from a timer, a signal handler, or code after an awaitSomething fires after the node is gone
A variable or Array holds the node and it breaks later in the gameA stored reference to a dead node
You use the node a line or two after freeing itUsing the node right after freeing it

Enemy has a Timer that calls take_damage() on itself every second. Main keeps a reference to the enemy.

  • MainNode2Dhas scriptholds a reference to Enemy
    • EnemyCharacterBody2Dhas scripthas take_damage()
      • TimerTimer

queue_free() removes the node at the end of the frame, but anything already scheduled still runs: a Timer that times out, a signal from another node, or the rest of a function that is waiting on an await. By the time it runs, the node it wants is gone.

main.gd
extends Node2D
@onready var enemy: CharacterBody2D = $Enemy
func hit_later() -> void:
await get_tree().create_timer(1.0).timeout
enemy.take_damage(10)

If the enemy is freed during that one second, the line after await hits a dead node.

Fix: check the node is still there before using it.

main.gd
func hit_later() -> void:
await get_tree().create_timer(1.0).timeout
enemy.take_damage(10)
if is_instance_valid(enemy):
enemy.take_damage(10)

When the code after the await is inside the node that gets freed, the simplest guard is on the node itself:

enemy.gd
func _on_timer_timeout() -> void:
if not is_inside_tree():
return
take_damage(1)

A variable like var target: Node2D or an Array of enemies keeps pointing at a node after it has been freed. Godot doesn’t set your variable to null for you, so the next time you use it, you get this error.

Fix: clear the reference when the node leaves. Every node emits tree_exited on its way out, so hook into that when you store it:

main.gd
var target: Node2D
func set_target(enemy: Node2D) -> void:
target = enemy
enemy.tree_exited.connect(func() -> void: target = null)

For an Array, drop the dead ones before you loop:

main.gd
var enemies: Array[Node2D] = []
func damage_all() -> void:
enemies = enemies.filter(is_instance_valid)
for enemy in enemies:
enemy.take_damage(10)

This is fine, even though it looks wrong:

main.gd
enemy.queue_free()
enemy.position = Vector2.ZERO

queue_free() only marks the node to be removed at the end of the current frame, so the node is still alive on the next line. free() is different. It deletes the node immediately, and anything after it on the same node breaks:

main.gd
enemy.free()
enemy.queue_free()

Fix: prefer queue_free(), and if you really need free(), make it the last thing you do with that node.

The error names the function that was called on the dead node, so you know what the node was. Search your scripts for every variable, Array and Dictionary that stores it, and look for the one that outlives the node. When you test for it, use is_instance_valid(node) rather than node == null. A freed reference is not null, it is a dead object, and only is_instance_valid() tells the two apart.