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

await, coroutines and timers

Pause GDScript functions with await, return values from coroutines, time events with create_timer and Timer nodes, and avoid the await mistakes that hang games.

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

What you will learn

  • Use await to pause a function on a signal, and predict where its caller goes on
  • Write coroutines that return values, and predict what await gives for a signal's arguments
  • Choose between create_timer, a Timer node and frame signals, and handle pause and Engine.time_scale
  • Prevent await mistakes such as freed targets, signals that come too early and a timer awaited without timeout

Before you start

What you’ll learn · Before you start

What you will learn

  • Use await to pause a function on a signal, and predict where its caller goes on
  • Write coroutines that return values, and predict what await gives for a signal's arguments
  • Choose between create_timer, a Timer node and frame signals, and handle pause and Engine.time_scale
  • Prevent await mistakes such as freed targets, signals that come too early and a timer awaited without timeout

Before you start

On this page

A half-second pause in a cutscene, written await get_tree().create_timer(0.5).timeout, lasted 30 frames at 60 frames a second. Written without .timeout, it lasted 0 frames, and the next line of dialogue came at once, with no error to say why. await lets a function stop in the middle and go on later, and that is how cutscenes, cooldowns and delayed effects are written in GDScript.

This page shows what await does to a function and to the code that called it, what it gives back, and how timers, frames, pause and time scale decide when a wait ends. Then come the ways a waiting function is caught out by a world that changed while it waited.

await pauses a function, and its caller goes on

countdown(from) prints a number, waits for the next frame with await get_tree().process_frame, and repeats. _ready() starts one countdown without await and a second one with it. A small counter numbers the frames from 1: it is connected to the same process_frame signal first, so it has already counted each new frame when the countdowns go on:

countdown.gd: one countdown on its own, one awaited GD · countdown.gd
extends Node

# Two countdowns that wait one frame between numbers. The first runs on its own; _ready() waits for the second.
var frame := 0


func _enter_tree() -> void:
	# Godot emits process_frame at the start of every frame. Connected first, this counter runs before any function
	# that waits for the same signal, so frame already holds the new frame's number when they go on.
	get_tree().process_frame.connect(func() -> void: frame += 1)


func _ready() -> void:
	print("_ready: starts countdown(3) without await, ", when())
	countdown(3)
	print("_ready: goes on at once, then awaits countdown(2)")
	var said: String = await countdown(2)
	print("_ready: countdown(2) returned \"", said, "\" ", when())
	await get_tree().process_frame
	get_tree().quit()


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


func countdown(from: int) -> String:
	for n in range(from, 0, -1):
		print("  countdown(", from, "): ", n, " ", when())
		await get_tree().process_frame
	print("  countdown(", from, ") is done ", when())
	return "go"


func when() -> String:
	return "before the first frame" if frame == 0 else "in frame %d" % frame
Two countdowns, frame by frame at --fixed-fps 60, Godot 4.7.2 Python · countdown_session.py
from session import files, run

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

Output

$ godot --headless --no-header --fixed-fps 60 countdown.tscn
_ready: starts countdown(3) without await, before the first frame
  countdown(3): 3 before the first frame
_ready: goes on at once, then awaits countdown(2)
  countdown(2): 2 before the first frame
  countdown(3): 2 in frame 1
  countdown(2): 1 in frame 1
_process in frame 1
  countdown(3): 1 in frame 2
  countdown(2) is done in frame 2
_ready: countdown(2) returned "go" in frame 2
_process in frame 2
  countdown(3) is done in frame 3
_process in frame 3

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

A function that contains await is a coroutine: A function that contains await. At each await it pauses and hands control back to its caller, and it goes on from the same line when the signal it waits for is emitted; await on the call waits for its return value.. When it reaches an await, it pauses and hands control back to whoever called it, and it goes on from the same line when the signal it waits for is emitted. Step through the run with that in mind:

Interactive · message by message Two countdowns and the code that waits for one Step through the run. countdown(3) runs on its own; _ready() waits for countdown(2). Arrows are calls and signals.
Two countdowns and the code that waits for one, step 6 of 6. Messages so far: _ready() to countdown(2): await countdown(2); Godot to countdown(2): process_frame; countdown(2) to _ready(): returns "go".Godotemits process_frame_ready()the callercountdown(2)printed 2, then 1await countdown(2)process_framereturns "go"

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

Every step as text
  1. countdown(3) without await. _ready() calls countdown(3) with no await. It prints 3, reaches its own await and hands control straight back, so _ready() goes on at once, before the first frame.
  2. countdown(2) with await. _ready() now calls var said = await countdown(2). The countdown prints 2 and pauses at its await; because _ready() awaits it, _ready() pauses too.
  3. Frame 1 begins. Godot emits process_frame at the start of frame 1. Both countdowns go on from their awaits; countdown(3) prints 2 and countdown(2) prints 1, then each waits again.
  4. Then the frame's _process. Code that waited for process_frame runs before the frame's _process(), which prints _process in frame 1 only after both countdowns have printed.
  5. countdown(2) returns. In frame 2 the loop of countdown(2) has nothing left. It prints done and returns "go", and the return goes to the code that awaited it.
  6. _ready() goes on. _ready() wakes up in frame 2 with said = "go". countdown(3), which nobody waits for, finishes on its own in frame 3.

countdown(3) printed 3 and paused, and _ready() went on at once, before the first frame. _ready() waited for countdown(2), because it wrote await in front of the call: a caller pauses only if it awaits. When frame 1 began, process_frame woke both countdowns, and only then did the frame’s _process() run. countdown(2) was done in frame 2, _ready() went on with its result, and countdown(3), which nobody waited for, finished in frame 3 on its own.

Key idea

await pauses only the function it is in. Its caller goes on at once unless it awaits the call too, and the paused function goes on from the same line when its signal is emitted.

Checkpoint

Check: What await does to a function and its caller

  1. One question _ready() calls countdown(3) without await, and countdown(3) prints 3 and then awaits process_frame. What does _ready() do now?

    Choose one answer.

    Show the answer: One question

    Answer: _ready() goes on with its next line at once, before the first frame

    A coroutine pauses itself, not its caller. Write await before the call when the caller must wait for it, as _ready() did for countdown(2).

A coroutine hands its result to the code that awaits it

A coroutine can return a value like any function; the caller gets it as the result of its await. await on a signal gives the values that came with the emit. results.gd tries each case, and no_await.gd uses a coroutine’s result without await:

results.gd: what await gives back GD · results.gd
extends Node

# What await gives back: a coroutine's return value, a signal's arguments, and a value that is neither.
signal bell_rang
signal coins_found(coins: int)
signal chest_opened(coins: int, by: String)


func _ready() -> void:
	print("await roll_die():         ", await roll_die())
	print("await double(4):          ", await double(4))
	ring_and_open.call_deferred()
	print("await bell_rang:          ", await bell_rang)
	print("await coins_found:        ", await coins_found)
	print("await chest_opened:       ", await chest_opened)
	get_tree().quit()


func roll_die() -> int:
	# A coroutine: it waits for the next frame, then returns.
	await get_tree().process_frame
	return 6


func double(n: int) -> int:
	# No await inside, so not a coroutine: await gives its result straight away.
	return n * 2


func ring_and_open() -> void:
	bell_rang.emit()
	coins_found.emit(5)
	chest_opened.emit(5, "Asha")
no_await.gd: a coroutine's result used without await GD · no_await.gd
extends Node

# The result of a coroutine used without await.


func _ready() -> void:
	var rolled: int = roll_die()
	print(rolled)


func roll_die() -> int:
	await get_tree().process_frame
	return 6
Return values, signal arguments, and a call Godot refuses, Godot 4.7.2 Python · results_session.py
from session import files, run

files("project.godot", "results.tscn", "results.gd", "no_await.gd")
# The second command checks a script that Godot refuses, on purpose.
run("""
godot --headless --no-header --fixed-fps 60 results.tscn
!godot --headless --no-header --check-only --script no_await.gd
""")

Output

$ godot --headless --no-header --fixed-fps 60 results.tscn
await roll_die():         6
await double(4):          8
await bell_rang:          <null>
await coins_found:        5
await chest_opened:       [5, "Asha"]
$ godot --headless --no-header --check-only --script no_await.gd
SCRIPT ERROR: Parse Error: Function "roll_die()" is a coroutine, so it must be called with "await".
          at: GDScript::reload (res://no_await.gd:7)
ERROR: Failed to load script "res://no_await.gd" with error "Parse error".
   at: load (modules/gdscript/gdscript_resource_format.cpp:46)

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

You await It gives In the run
a coroutine, await roll_die() its return value, when it returns 6, a frame later
a function without await in it, await double(4) its return value, at once 8
a signal with no arguments null <null>
a signal with one argument that value 5
a signal with two or more an Array of them [5, "Asha"]

The three signals were emitted one after the other in a single call, and each await still caught its own: emitting a signal wakes a waiting function at once, inside the emit, so results.gd was already waiting for the next signal when it came. Using a coroutine’s result without await is refused before the game runs: Function "roll_die()" is a coroutine, so it must be called with "await". Calling a coroutine as a statement, as _ready() did with countdown(3), is allowed, and starts it without waiting.

Checkpoint

Check: What await gives back

  1. One question chest_opened(coins: int, by: String) is emitted as chest_opened.emit(5, "Asha"). What does await chest_opened give?

    Choose one answer.

    Show the answer: One question

    Answer: [5, "Asha"], an Array of both values

    No arguments: null. One argument: that value. More than one: an Array, in the order of the emit.

Timers, frames and time scale decide when a wait ends

Most waits in a game are for time. get_tree().create_timer(seconds) makes a SceneTreeTimer: The one-shot timer that get_tree().create_timer(seconds) makes, awaited by its timeout signal. It needs no node, keeps counting while the game is paused unless created with false as the second argument, and cannot be stopped., a one-shot timer you await by its timeout signal; a Timer node lives in the scene, can repeat and can be stopped. timers.gd measures both in frames, paused.gd pauses the game for 30 frames while four waits run, and frame_order.gd sets up four kinds of wait in one frame to see where each goes on:

timers.gd: create_timer(), time scale and two Timer nodes GD · timers.gd
extends Node

# Three ways to wait, measured in frames at --fixed-fps 60.
var frame := 0
var half_timeouts := 0
var tiny_timeouts := 0


func _enter_tree() -> void:
	get_tree().process_frame.connect(func() -> void: frame += 1)


func _ready() -> void:
	await get_tree().process_frame
	var start := frame
	print("frame ", frame, ": create_timer(0.25)")
	await get_tree().create_timer(0.25).timeout
	print("frame ", frame, ": it ran out, ", frame - start, " frames later")
	Engine.time_scale = 2.0
	start = frame
	await get_tree().create_timer(0.25).timeout
	print("with Engine.time_scale 2.0, the same timer took ", frame - start, " frames")
	Engine.time_scale = 1.0
	$Half.timeout.connect(func() -> void: half_timeouts += 1)
	$Tiny.timeout.connect(func() -> void: tiny_timeouts += 1)
	$Half.start()
	$Tiny.start()
	for i in 100:
		await get_tree().process_frame
	print("in the next 100 frames, Half (0.5 s) ran out ", half_timeouts, " times and Tiny (0.01 s) ",
			tiny_timeouts, " times")
	get_tree().quit()
paused.gd: four waits of a quarter of a second during a pause GD · paused.gd
extends Node

# The game is paused for frames 1 to 30. Which waits of a quarter of a second keep counting?
var frame := 0


func _enter_tree() -> void:
	get_tree().process_frame.connect(func() -> void: frame += 1)


func _ready() -> void:
	process_mode = Node.PROCESS_MODE_ALWAYS
	get_tree().paused = true
	print("frame 0: the game is paused, and four waits of 0.25 s start")
	get_tree().create_timer(0.25).timeout.connect(_report.bind("create_timer(0.25)"))
	get_tree().create_timer(0.25, false).timeout.connect(_report.bind("create_timer(0.25, false)"))
	$Pausable.timeout.connect(_report.bind("the Timer node"))
	$Pausable.start()
	_count_frames(15)


func _process(_delta: float) -> void:
	if frame == 30:
		print("frame 30: the game goes on")
		get_tree().paused = false
	elif frame == 60:
		get_tree().quit()


func _count_frames(count: int) -> void:
	for i in count:
		await get_tree().process_frame
	_report("15 frames of await process_frame")


func _report(which: String) -> void:
	print("  ", which, " ran out in frame ", frame)
frame_order.gd: four waits set up in frame 2 GD · frame_order.gd
extends Node

# In frame 2, four kinds of wait are set up at once. The prints show where each one goes on.
var frame := 0


func _enter_tree() -> void:
	get_tree().process_frame.connect(func() -> void: frame += 1)


func _process(_delta: float) -> void:
	if frame == 2:
		print("frame 2, _process: sets up four waits")
		_deferred.call_deferred()
		get_tree().create_timer(0.001).timeout.connect(func() -> void: print("  a 0.001 s timer ran out, frame ", frame))
		_after_physics()
		_after_process_frame()
		print("frame 2, _process ends")
	elif frame == 3:
		print("frame 3, _process")
		get_tree().quit()


func _deferred() -> void:
	print("  the deferred call runs, frame ", frame)


func _after_physics() -> void:
	await get_tree().physics_frame
	print("  after await physics_frame, the next physics tick: before frame 3's process_frame")


func _after_process_frame() -> void:
	await get_tree().process_frame
	print("  after await process_frame, frame ", frame)
Timers measured in frames, a pause, and the order of one frame, Godot 4.7.2 Python · timers_session.py
from session import files, run

files("project.godot", "timers.tscn", "timers.gd", "paused.tscn", "paused.gd", "frame_order.tscn", "frame_order.gd")
run("""
godot --headless --no-header --fixed-fps 60 timers.tscn
godot --headless --no-header --fixed-fps 60 paused.tscn
godot --headless --no-header --fixed-fps 60 frame_order.tscn
""")

Output

$ godot --headless --no-header --fixed-fps 60 timers.tscn
frame 1: create_timer(0.25)
frame 16: it ran out, 15 frames later
with Engine.time_scale 2.0, the same timer took 8 frames
in the next 100 frames, Half (0.5 s) ran out 3 times and Tiny (0.01 s) 99 times
$ godot --headless --no-header --fixed-fps 60 paused.tscn
frame 0: the game is paused, and four waits of 0.25 s start
  15 frames of await process_frame ran out in frame 15
  create_timer(0.25) ran out in frame 16
frame 30: the game goes on
  the Timer node ran out in frame 45
  create_timer(0.25, false) ran out in frame 45
$ godot --headless --no-header --fixed-fps 60 frame_order.tscn
frame 2, _process: sets up four waits
frame 2, _process ends
  the deferred call runs, frame 2
  a 0.001 s timer ran out, frame 2
  after await physics_frame, the next physics tick: before frame 3's process_frame
  after await process_frame, frame 3
frame 3, _process

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

A quarter of a second took 15 frames at 60 frames a second. With Engine.time_scale at 2.0 it took 8, because the time scale: Engine.time_scale, how fast the game's clock runs compared with real time. At 2.0, timers and delta run twice as fast; audio playback does not change. The docs advise keeping it above 0.0. speeds up the game’s clock, and with it every timer and every delta; the Engine class reference adds that audio is not affected. A repeating Timer of 0.01 s ran out 99 times in 100 frames, never more than once a frame, where a perfect clock would count about 167: the Timer docs warn that a timer processes at most once per frame and advise your own code for waits under about 0.05 s.

The pause changed more. The default create_timer(0.25) kept counting and ran out in frame 16, inside the pause, and a loop of 15 await get_tree().process_frame ended in frame 15, because process_frame is emitted while the game is paused. create_timer(0.25, false) and the pausable Timer node waited, and ran out in frame 45, 15 frames after the pause ended. The last run shows where each kind of wait goes on inside the frames:

Interactive · stage by stage Where waiting code goes on, within the frames Step through frames 2 and 3 of frame_order.tscn. Each stage is a point of the frame; its sub-label says what ran there.
Where waiting code goes on, within the frames, step 6 of 6. Showing Physics tick (physics_frame coroutine), process_frame (process_frame coroutine), _process(), tree order (frame 3, _process()), Deferred calls (_deferred(), frame 2), Timers (0.001 s timer, frame 2). In focus: _process(), tree order.next framePhysics tickphysics_frame coroutineprocess_frameprocess_frame coroutine_process(), tree orderframe 3, _process()Deferred calls_deferred(), frame 2Timers0.001 s timer, frame 2

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

Every step as text
  1. Frame 2 sets up four waits. In frame 2, _process() calls _deferred.call_deferred(), makes a 0.001 s timer, and starts two coroutines, one waiting for physics_frame and one for process_frame. Then _process() ends.
  2. The deferred call. With every node processed, Godot runs the deferred calls. The run printed "the deferred call runs, frame 2" right after "_process ends".
  3. The timer. Then timers are updated. The 0.001 s timer is shorter than a frame, so it ran out at once, still in frame 2. No timer can run out between frames.
  4. The next physics tick. The next frame starts with its physics tick, before process_frame. The coroutine waiting for physics_frame went on there, before frame 3 began.
  5. process_frame, frame 3. Godot emits process_frame, and the other coroutine goes on, in frame 3, before any _process() of that frame.
  6. Then _process(). Only then does frame 3's _process() run. Code after an await of process_frame always runs before the frame's _process() calls.

Choose the way of waiting by what it must do. Answer the questions under the table:

Interactive · answer and compare Which way of waiting fits your case? Answer the questions about what you are timing; each answer rules out the ways that would get it wrong.
Must the wait stop while the game is paused?
Can the wait be called off before it ends, such as a cooldown reset?
Does it repeat, such as a spawn every 2 seconds?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

Four ways to wait, against pause, stopping early, repeating and time scale
Way to wait While the game is paused Stopped early Repeating Engine.time_scale
create_timer() Written as: await get_tree().create_timer(0.25).timeout Poor keeps counting: ran out in frame 16, inside the pause Poor has no stop(); its timeout still comes Mixed one wait only: make a new one each time Good follows it: 8 frames at 2.0 instead of 15
create_timer(…, false) Written as: await get_tree().create_timer(0.25, false).timeout Good waits: ran out in frame 45, 15 frames after the pause Poor has no stop(); its timeout still comes Mixed one wait only: make a new one each time Good follows it, as without false
Timer node Written as: $Cooldown.start(), then its timeout signal Good stops with its node: ran out in frame 45 Good stop() ends it, without a timeout Good repeats unless one_shot; at most once a frame Good follows it, unless ignore_time_scale is on
Counting frames Written as: for i in 15: await get_tree().process_frame Poor process_frame comes while paused: done in frame 15 Mixed only with a flag the loop checks Mixed a loop you write yourself Poor ignores it, and 15 frames last twice as long at 30 frames a second
How the answers decide
  • “Must the wait stop while the game is paused?” Yes, it is part of gameplay: rules out create_timer() and Counting frames. The default create_timer() and a loop of process_frame both kept going while the game was paused.
  • “Must the wait stop while the game is paused?” No, it runs in a pause menu or a loading screen: rules out create_timer(…, false) and adds a caveat to Timer node. A Timer node stops with a paused tree unless its process_mode is set to always; create_timer(…, false) always stops.
  • “Can the wait be called off before it ends, such as a cooldown reset?” Yes, it may be stopped early: rules out create_timer() and create_timer(…, false) and adds a caveat to Counting frames. An awaited SceneTreeTimer has no stop(), so the code after the await runs anyway; a Timer node has stop().
  • “Does it repeat, such as a spawn every 2 seconds?” Yes, again and again: adds a caveat to create_timer(), create_timer(…, false) and Counting frames. A Timer node repeats by itself; the others need a loop around the await.

New in Godot 4.7

Tween.tween_await(signal) makes a tween wait until a signal is emitted before its next step, which suits animated cutscenes. The Tween class reference suggests AwaitTweener.set_timeout() when the signal may never come.

Checkpoint

Check: Timers, frames, pause and time scale

  1. Question 1 of 2 At --fixed-fps 60 with Engine.time_scale set to 2.0, about how many frames does await get_tree().create_timer(0.5).timeout take?

    Type a number.

    A hint

    A 0.25 s timer took 15 frames at time scale 1 and 8 at time scale 2.

    Show the answer: Question 1 of 2

    Answer: 15 frames (anything from 14 to 16 counts)

    At time scale 2, every frame moves the game's clock 2/60 s, so half a second of game time passes in 15 frames, half the 30 it takes at 1.0. The run's quarter of a second took 8 frames instead of 15.

  2. Question 2 of 2 A power-up lasts 10 s: await get_tree().create_timer(10.0).timeout, then it ends. Players pause the game for a while. What goes wrong?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The power-up ends during the pause, because the default create_timer() keeps counting while the game is paused

    Pass false as the second argument, create_timer(10.0, false), or use a Timer node in the gameplay tree, which stops with the pause.

The world can change while a function waits

Between an await and the line after it, a whole game goes on. cave.gd starts four functions that wait, then changes the world under them: after a tenth of a second it frees the player that three bats aim at, and frees one of the bats. Its cutscene emits gate_opened and only then waits for it:

bat.gd: a swoop that looks first, and one that does not GD · bat.gd
extends Node

# A bat that waits half a second, then swoops at its target. The second version looks before it swoops.


func swoop_at(target: Node2D) -> void:
	print("  ", name, ": waits 0.5 s, then swoops at ", target.name)
	await get_tree().create_timer(0.5).timeout
	print("  ", name, ": swoops at ", target.name)


func swoop_carefully_at(target: Node2D) -> void:
	print("  ", name, ": waits 0.5 s, then swoops at ", target.name)
	await get_tree().create_timer(0.5).timeout
	if not is_instance_valid(target):
		print("  ", name, ": its target is gone, so it flies off")
		return
	print("  ", name, ": swoops at ", target.name)
cave.gd: the player and a bat are freed while they wait GD · cave.gd
extends Node

# The world changes while four functions wait: a target and a bat are freed, and a signal comes too early.
signal gate_opened

var frame := 0


func _enter_tree() -> void:
	get_tree().process_frame.connect(func() -> void: frame += 1)


func _ready() -> void:
	var player := Node2D.new()
	player.name = "Player"
	add_child(player)
	$Bat1.swoop_at(player)
	$Bat2.swoop_carefully_at(player)
	$Bat3.swoop_at(player)
	open_gate_and_wait()
	await get_tree().create_timer(0.1).timeout
	print("frame ", frame, ": the player and Bat3 are freed")
	player.free()
	$Bat3.free()
	await get_tree().create_timer(1.0).timeout
	print("frame ", frame, ": the cutscene is still waiting for the gate; the run ends")
	get_tree().quit()


func open_gate_and_wait() -> void:
	print("  cutscene: opens the gate, then waits for gate_opened")
	gate_opened.emit()
	await gate_opened
	print("  cutscene: the gate is open")
Four waits that meet a changed world, Godot 4.7.2 Python · cave_session.py
from session import files, run

files("project.godot", "cave.tscn", "cave.gd", "bat.gd")
# Bat1 uses a freed target after its wait, so the run reports a script error on purpose.
run("""
!godot --headless --no-header --fixed-fps 60 cave.tscn
""")

Output

$ godot --headless --no-header --fixed-fps 60 cave.tscn
  Bat1: waits 0.5 s, then swoops at Player
  Bat2: waits 0.5 s, then swoops at Player
  Bat3: waits 0.5 s, then swoops at Player
  cutscene: opens the gate, then waits for gate_opened
frame 7: the player and Bat3 are freed
SCRIPT ERROR: Invalid access to property or key 'name' on a base object of type 'previously freed'.
          at: swoop_at (res://bat.gd:9)
          GDScript backtrace (most recent call first):
              [0] swoop_at (res://bat.gd:9)
  Bat2: its target is gone, so it flies off
frame 67: the cutscene is still waiting for the gate; the run ends

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

Each bat met its own outcome. Bat1 woke up after half a second and read target.name from a target that was gone: previously freed, and its function stopped. Bat2 checked with is_instance_valid() first and flew off. Bat3 printed nothing more, because it had been freed itself: a freed object’s coroutines never go on, and Godot drops them without a word. The cutscene waited until the run ended, because an await hears only emissions that come after it starts. Nothing reported that either.

What happened during the wait What the waiting function did What to write
an object it uses was freed went on, and failed on the freed object is_instance_valid() after the await
its own object was freed never went on, silently nothing, if that is what you want
the signal came before the await waited for good, silently check the state before you wait

await on the timer instead of its timeout

await get_tree().create_timer(0.5) awaits the timer object, which is not a signal, so it gives the timer back at once and waits for nothing. The running game prints nothing; only the script editor’s list of warnings says the await is unnecessary. Write await get_tree().create_timer(0.5).timeout. The practice below catches this slip by the frames its steps start on.

Checkpoint

Check: Await mistakes in a changing world

  1. Question 1 of 2 Bat1 awaits a 0.5 s timer, then reads target.name. The target was freed while it waited. What happens?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: An error, Invalid access to property or key 'name' on a base object of type 'previously freed'

    Anything may have changed during the wait. Check references with is_instance_valid() after an await, as Bat2 did.

  2. Question 2 of 2 open_gate_and_wait() emits gate_opened and then writes await gate_opened. What happens?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It waits for good, because the signal was emitted before the await began

    Check the state before you wait (if gate.is_open: return), or start waiting before anything can emit the signal.

Practise: a cutscene that waits its turn

cutscene.gd must play a list of steps in order, each starting when the one before it has finished. A step is a Dictionary with one key: {"wait": 0.5} waits half a second, {"signal": door_opened} waits for that signal, and {"call": callable} calls it. Each step appends the frame it starts on to started, and finished is emitted after the last.

  1. Next to an empty project.godot, save the starter cutscene.gd and check_cutscene.gd below.
  2. Write play(); the starter already counts frames, as the examples above do.
  3. Start the checker in a terminal in that folder: godot --headless --no-header --fixed-fps 60 --script check_cutscene.gd.
cutscene.gd: the starter file GD · practice/cutscene.gd
extends Node

# Plays a cutscene: a list of steps, each one started when the step before it has finished.
# A step is a Dictionary with one key: {"wait": 0.5} waits that many seconds, {"signal": some_signal} waits until the
# signal is emitted, and {"call": some_callable} calls it. Each step appends the frame it starts on to started, and
# finished is emitted once the last step is done.
# Your task: write play().
signal finished

var frame := 0
var started: Array[int] = []


func _enter_tree() -> void:
	get_tree().process_frame.connect(func() -> void: frame += 1)


func play(steps: Array[Dictionary]) -> void:
	pass # Replace this line with your code.
check_cutscene.gd: the checker of this task GD · practice/check_cutscene.gd
extends SceneTree

# Checks cutscene.gd. Run it in the project's folder:
#   godot --headless --no-header --fixed-fps 60 --script check_cutscene.gd
# It plays six steps from frame 1 of the cutscene; the door opens in its frame 70. Each step must start on its frame.
signal door_opened

const WANT: Array[int] = [1, 1, 31, 31, 70, 85]
var cutscene: Node
var finished_at := -1
var said: Array[String] = []
var passed := 0
var total := 0


func _initialize() -> void:
	cutscene = load("res://cutscene.gd").new()
	cutscene.tree_entered.connect(_on_cutscene_entered)
	root.add_child.call_deferred(cutscene)


func _on_cutscene_entered() -> void:
	# Connected after the cutscene's own frame counter, so cutscene.frame is already this frame's number here.
	process_frame.connect(_on_frame)
	cutscene.finished.connect(func() -> void: finished_at = cutscene.frame)


func _on_frame() -> void:
	if cutscene.frame == 1:
		var steps: Array[Dictionary] = [
			{"call": say.bind("Asha: Is anyone here?")},
			{"wait": 0.5},
			{"call": say.bind("The door creaks.")},
			{"signal": door_opened},
			{"wait": 0.25},
			{"call": say.bind("Ravi: Come in.")},
		]
		cutscene.play(steps)
	elif cutscene.frame == 70:
		print("frame 70: the door opens")
		door_opened.emit()
	elif cutscene.frame == 100:
		check(cutscene.started == WANT, "the steps start in frames %s (want %s)" % [cutscene.started, WANT])
		check(said.size() == 3, "all three lines were said")
		check(finished_at == 85, "finished is emitted in frame 85 (it was %d)" % finished_at)
		print("%d of %d checks passed" % [passed, total])
		quit(0 if passed == total else 1)


func say(line: String) -> void:
	said.append(line)
	print("frame %d: %s" % [cutscene.frame, line])


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

The checker plays six steps from frame 1 and opens the door in frame 70, so the steps must start in frames 1, 1, 31, 31, 70 and 85. Its output for a finished play() and for one that awaits the timer object itself:

The checker on a finished play() and on a tempting one, Godot 4.7.2 Python · check_session.py
from session import files, run

# The checker on a cutscene.gd that solves the task, then on one that awaits the timer instead of its timeout.
files("practice/project.godot=right/project.godot", "practice/check_cutscene.gd=right/check_cutscene.gd",
      "practice/solution/cutscene.gd=right/cutscene.gd",
      "practice/project.godot=tempting/project.godot", "practice/check_cutscene.gd=tempting/check_cutscene.gd",
      "practice/tempting/cutscene.gd=tempting/cutscene.gd")
run("""
cd right
godot --headless --no-header --fixed-fps 60 --script check_cutscene.gd
cd ../tempting
!godot --headless --no-header --fixed-fps 60 --script check_cutscene.gd
""")

Output

$ cd right
$ godot --headless --no-header --fixed-fps 60 --script check_cutscene.gd
frame 1: Asha: Is anyone here?
frame 31: The door creaks.
frame 70: the door opens
frame 85: Ravi: Come in.
PASS the steps start in frames [1, 1, 31, 31, 70, 85] (want [1, 1, 31, 31, 70, 85])
PASS all three lines were said
PASS finished is emitted in frame 85 (it was 85)
3 of 3 checks passed
$ cd ../tempting
$ godot --headless --no-header --fixed-fps 60 --script check_cutscene.gd
frame 1: Asha: Is anyone here?
frame 1: The door creaks.
frame 70: the door opens
frame 70: Ravi: Come in.
FAIL the steps start in frames [1, 1, 1, 1, 70, 70] (want [1, 1, 31, 31, 70, 85])
PASS all three lines were said
FAIL finished is emitted in frame 85 (it was 70)
1 of 3 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 play() started the line after each wait in the same frame, 1 and 70, because each of its waits ended at once. A finished one passes 3 of 3. With that done, practise the four ideas of the page below: the weakest comes first, and each answer explains itself the moment you choose it:

Practice that adapts to you

Practise this lesson

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

  1. Question 1 of 12 What makes a GDScript function a coroutine?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: It contains an await

    A coroutine can pause at each await and go on later, when what it waits for has happened.

  2. Question 2 of 12 A coroutine writes await get_tree().process_frame in frame 2. When does its next line run?

    Choose one answer.

    Show the answer: Question 2 of 12

    Answer: At the start of frame 3, before the _process() calls of frame 3

    Code after an await of process_frame runs once per frame, before any _process() of that frame; physics_frame comes earlier still, with the frame's physics tick.

  3. Question 3 of 12 Two coroutines both await countdown_done, one signal. It is emitted once. What happens?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: Both go on, in the order they started to wait

    This is why the countdown run shows countdown(3) and countdown(2) both going on with the same process_frame.

  4. Question 4 of 12 double(4) contains no await and returns 8. What does await double(4) do?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: Gives 8 at once, without pausing

    A caller can await a function that may or may not be a coroutine; it pauses only when the function does.

  5. Question 5 of 12 What does await bell_rang give, where signal bell_rang has no parameters?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: null

    await on a signal gives null, its one argument, or an Array of its arguments.

  6. Question 6 of 12 roll_die() awaits a frame and returns 6. What does Godot 4.7.2 do with var rolled: int = roll_die()?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: It refuses the script with a parse error, "is a coroutine, so it must be called with await"

    Calling a coroutine as a statement, roll_die(), is allowed and starts it without waiting; using its result needs await.

  7. Question 7 of 12 A repeating Timer has a wait_time of 0.01 s. At --fixed-fps 60, how often did it run out in the next 100 frames?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: 99 times, at most once a frame, not about 167 times

    The Timer docs advise writing your own code for waits shorter than about 0.05 s, because one frame is the smallest step a timer can take.

  8. Question 8 of 12 What does Engine.time_scale = 2.0 change?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: Timers, SceneTreeTimers and the delta of _process() and _physics_process() run twice as fast; audio does not

    Keep time_scale above 0.0, as the docs recommend, and pass ignore_time_scale to timers that must run in real time.

  9. Question 9 of 12 In frame 2, _process() calls call_deferred(), makes a 0.001 s timer, and starts a coroutine that awaits process_frame. Put what the run printed in order.

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

    Show the answer: Question 9 of 12

    Answer:

    1. frame 2, _process ends
    2. the deferred call runs
    3. the 0.001 s timer runs out
    4. the coroutine goes on, in frame 3
    5. frame 3, _process

    After _process() come the deferred calls, then the timers, all in frame 2. The next frame begins with its physics tick and process_frame, and only then frame 3's _process() runs.

  10. Question 10 of 12 A step writes await get_tree().create_timer(0.5), without .timeout. How long does it wait?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: Not at all; await gives the timer back at once, and Godot prints nothing

    Await the signal: await get_tree().create_timer(0.5).timeout. The checker of the practice catches the slip by the frames the steps start on.

  11. Question 11 of 12 A coroutine awaits a signal that is never emitted. What does Godot report?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: Nothing; the function simply never goes on

    Because nothing reports it, a stuck await is found by printing before and after it, or by a test that checks the frame the next step starts on.

  12. Question 12 of 12 This turret may be freed, and so may its target, during the wait. Which line belongs where the comment is?

    Read the code, then choose one answer.

    func fire_at(target: Node2D) -> void:
    	await get_tree().create_timer(0.3).timeout
    	# which line?
    	spawn_bullet_towards(target.global_position)
    Show the answer: Question 12 of 12

    Answer: if not is_instance_valid(target): return

    After any await, check the objects the function needs before it uses them. Bat2 did that and flew off instead of failing.

Interview questions

Warm-up: what does await do in GDScript?

await pauses the function it is written in until a signal is emitted or a coroutine returns, then goes on from the same line. A function that contains await becomes a coroutine: at the first await it hands control back to its caller, which goes on at once unless it awaits the call too. On Godot 4.7.2, a countdown that waited for process_frame between numbers printed its first number before the first frame and the rest one frame apart, while its caller had moved on. await gives back the coroutine’s return value, or the signal’s argument, null for no arguments or an Array for several. Typical uses are await get_tree().create_timer(0.5).timeout for a delay and await animation_player.animation_finished in a cutscene. Using a coroutine’s result without await is a parse error.

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

  1. Fresher to mid

    A cutscene calls gate.open() and then await gate.opened before the next line of dialogue. On most playthroughs it works; when the player has opened the gate earlier, the cutscene freezes for good, and nothing appears in the output. Find the cause and fix it.

    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 speed power-up lasts 10 seconds: activate(), then await get_tree().create_timer(10.0).timeout, then deactivate(). Players report that pausing the game eats into the power-up, and that it sometimes runs out while the pause menu is open. Explain and fix it, and say what else in the game probably has the same bug.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • await pauses only the function it is in, which becomes a coroutine; its caller goes on at once unless it awaits too, and the coroutine goes on from the same line when its signal is emitted.
  • await gives a coroutine’s return value, a function’s result at once, null for a signal without arguments, the value of one argument, or an Array of several, and a coroutine’s result needs await.
  • create_timer(0.25) took 15 frames at 60 frames a second and 8 at time scale 2.0, kept counting through a pause unless made with false, and no timer runs out more than once a frame.
  • During a wait, objects can be freed and signals can come early: check references with is_instance_valid() after an await, check the state before waiting, and await a timer’s timeout, not the timer.

Terms in this lesson

coroutine
A function that contains await. At each await it pauses and hands control back to its caller, and it goes on from the same line when the signal it waits for is emitted; await on the call waits for its return value.
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.
SceneTreeTimer
The one-shot timer that get_tree().create_timer(seconds) makes, awaited by its timeout signal. It needs no node, keeps counting while the game is paused unless created with false as the second argument, and cannot be stopped.
time scale
Engine.time_scale, how fast the game's clock runs compared with real time. At 2.0, timers and delta run twice as fast; audio playback does not change. The docs advise keeping it above 0.0.

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.