Quick answer: The _ready function is called bottom-up in the scene tree. Children are ready before their parents. If a parent's _ready tries to access a sibling node or a node that has not been added to the tree yet, get_node returns null.

Here is how to fix Godot scene tree null during ready. Your game crashes on startup with “Invalid call. Nonexistent function on a previously freed instance” or you get null when calling get_node() inside _ready(). The node exists in the scene tree — you can see it right there in the editor — but at runtime, the reference comes back null. This is Godot’s node initialization order at work, and once you understand it, you will never hit this bug again.

The Symptom

During scene initialization, your script tries to access another node using get_node(), $NodePath, or a variable assigned at the top of the script. The result is null, and any method call on it crashes the game with an error about calling a function on a null value or a nonexistent instance.

This typically happens in _ready(), but it can also occur in _init() or in variable initializers at the script level. The error is consistent — it happens every time the scene loads, not intermittently. This distinguishes it from race conditions, which are timing-dependent.

The most confusing variant is when the reference works on some nodes but not others within the same scene. A parent’s _ready() can access its children, but it cannot reliably access sibling nodes or nodes in other branches. This inconsistency makes the bug seem random until you understand the initialization order.

Another variant is the reference working in the editor (for @tool scripts) but failing at runtime, or working when you test the scene individually but failing when the scene is loaded as part of a larger scene tree. These point to the same initialization order issue but manifested through different loading contexts.

How Node Initialization Order Works

Godot initializes nodes in a specific order when a scene is loaded. Understanding this order is the key to fixing null reference bugs:

1. _init() is called when the node object is created in memory. At this point, the node is not in the scene tree yet. It has no parent, no children, and no path. Calling get_node() or get_tree() here will always return null.

2. Children are added. Godot adds child nodes to the tree from the scene file, recursively. Each child goes through _init and then gets added to its parent.

3. _ready() is called bottom-up. The deepest leaves of the tree get their _ready() called first, then their parents, then the grandparents, all the way up to the root of the scene. This means that when a parent’s _ready() runs, all of its children are already initialized and ready.

4. _enter_tree() is called top-down. This is the opposite of _ready() — parents enter the tree before their children. If you need to access the tree structure during initialization, _enter_tree() is called earlier but children may not be ready yet.

The critical implication: when _ready() runs on Node A, all of Node A’s children are guaranteed to be ready. But Node A’s siblings (other children of Node A’s parent) may or may not be ready, depending on their order in the scene tree. And nodes in completely different branches of the tree may not be ready at all.

Using @onready Correctly

The @onready annotation is the standard solution for referencing child nodes. It defers the variable assignment until _ready() is called, ensuring the children exist:

extends CharacterBody2D

# WRONG: Evaluated when the script loads, before _ready
var sprite = $Sprite2D  # Will be null!

# CORRECT: Evaluated during _ready, when children exist
@onready var sprite = $Sprite2D
@onready var collision = $CollisionShape2D
@onready var anim_player = $AnimationPlayer

func _ready():
  # These are all safe - children are ready before parent
  sprite.texture = load("res://player.png")
  anim_player.play("idle")

The @onready annotation is syntactic sugar for assigning the variable at the beginning of _ready(). It runs before any code you write in _ready(), so the variables are available immediately when your _ready() function starts executing.

However, @onready only helps with child nodes. If you need to reference a node that is not a child (a sibling, a node in another branch, or an autoload), @onready may still return null because that node might not be ready yet when your _ready() runs.

A common mistake is using @onready with a path that goes up the tree and then back down to a sibling:

# RISKY: Sibling may not be ready yet
@onready var hud = $"../HUD"  # Depends on scene tree order

# SAFER: Use call_deferred or groups
var hud: Control

func _ready():
  call_deferred("_setup_references")

func _setup_references():
  hud = get_node("../HUD")
  print("HUD found: ", hud != null)

call_deferred and Deferred Initialization

call_deferred() schedules a method to run at the end of the current frame, after all _ready() calls have completed and before the first _process() frame. This makes it the safest way to access nodes across different branches of the scene tree.

extends CharacterBody2D

var world_manager: Node
var game_camera: Camera2D

func _ready():
  # Child references are safe here
  var sprite = $Sprite2D  # OK

  # Cross-branch references should be deferred
  call_deferred("_deferred_init")

func _deferred_init():
  # All nodes in the scene are now ready
  world_manager = get_node("/root/WorldManager")
  game_camera = get_tree().get_first_node_in_group("camera")
  print("Deferred init complete")

The deferred call runs after the scene tree is fully initialized but before the first frame of gameplay. This means there is no visual delay or skipped frame — the initialization just happens in the correct order.

An alternative to call_deferred is using await get_tree().process_frame. This pauses the function until the next process frame, which is slightly later than a deferred call. Both approaches work, but call_deferred is more predictable because it runs at a specific point in the frame lifecycle.

For autoloads (singletons), the situation is simpler. Autoloads are added to the scene tree before any scene is loaded, so they are always ready when your scene’s _ready() runs. You can safely access autoloads from _ready() without deferring:

# Accessing an autoload is always safe in _ready
func _ready():
  var game_state = get_node("/root/GameState")  # Autoload - always available
  game_state.register_player(self)

Signals as an Alternative to Direct References

For complex scenes where initialization order is unpredictable, signals provide a cleaner alternative to direct node references. Instead of one node reaching into another node’s branch, nodes emit signals and let interested parties connect to them.

# Player.gd - emits a signal when ready
extends CharacterBody2D

signal player_ready(player)

func _ready():
  # Set up own children first
  $Sprite2D.texture = load("res://player.png")
  # Then announce that we are ready
  player_ready.emit(self)


# HUD.gd - waits for the player signal
extends Control

var player: CharacterBody2D

func _ready():
  # Connect to the player's signal when it becomes ready
  var p = get_tree().get_first_node_in_group("player")
  if p:
    p.player_ready.connect(_on_player_ready)

func _on_player_ready(p: CharacterBody2D):
  player = p
  print("HUD linked to player")

Groups are another way to find nodes without hardcoded paths. Add nodes to groups in the editor or in _ready() using add_to_group(), then find them with get_tree().get_nodes_in_group(). This approach is more resilient to scene structure changes than get_node() with fixed paths.

The signal approach completely eliminates initialization order issues because the connection is event-driven. The HUD does not need the player to be ready at any specific time — it simply responds whenever the player announces itself. This pattern scales well to complex scenes with many interconnected nodes.

"If you find yourself writing ../../../SomeNode in get_node, stop and rethink. Use groups or signals. Hard-coded paths break the moment you reorganize the scene tree."

Related Issues

If your nodes are null not because of initialization order but because of cyclic script references, see class_name cyclic reference errors. For signals that are connected in _ready but never fire, custom signal connection issues covers the Godot 4 callable syntax. If your null errors only happen after scene changes, autoload singleton access explains why node references from the old scene become invalid.

Children are ready before parents. Siblings are not guaranteed.