Why do I get "previously freed instance"?
The error
Section titled “The error”You’ll see something like this in the Debugger panel. The function name will be your own:
Which cause is it?
Section titled “Which cause is it?”| What’s happening | Go to |
|---|---|
The error comes from a timer, a signal handler, or code after an await | Something fires after the node is gone |
| A variable or Array holds the node and it breaks later in the game | A stored reference to a dead node |
| You use the node a line or two after freeing it | Using the node right after freeing it |
The example scene
Section titled “The example scene”Enemy has a Timer that calls take_damage() on itself every second. Main keeps a reference to the enemy.
MainNode2D
holds a reference to Enemy
EnemyCharacterBody2D
has take_damage()
TimerTimer
Something fires after the node is gone
Section titled “Something fires after the node is gone”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.
extends Node2D
@onready var enemy: CharacterBody2D = $Enemy
func hit_later() -> void: await get_tree().create_timer(1.0).timeout enemy.take_damage(10)using Godot;
public partial class Main : Node2D{ private Enemy _enemy;
public override void _Ready() { _enemy = GetNode<Enemy>("Enemy"); }
private async void HitLater() { await ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout); _enemy.TakeDamage(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.
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)private async void HitLater(){ await ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout); _enemy.TakeDamage(10); if (IsInstanceValid(_enemy)) _enemy.TakeDamage(10);}When the code after the await is inside the node that gets freed, the simplest guard is on the node itself:
func _on_timer_timeout() -> void: if not is_inside_tree(): return take_damage(1)A stored reference to a dead node
Section titled “A stored reference to a dead node”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:
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:
var enemies: Array[Node2D] = []
func damage_all() -> void: enemies = enemies.filter(is_instance_valid) for enemy in enemies: enemy.take_damage(10)Using the node right after freeing it
Section titled “Using the node right after freeing it”This is fine, even though it looks wrong:
enemy.queue_free()enemy.position = Vector2.ZEROqueue_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:
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.
Still stuck?
Section titled “Still stuck?”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.