Unable to obtain iterator object of type 'Object' (but was obtained on first iteration?) in Godot 4

By Eduardo Orellana · Updated

The for loop got its first value from the object, but on a later pass the call to the object's _iter_get() failed, so the loop stopped. Keep the object able to answer _iter_init(), _iter_next() and _iter_get() until the loop ends: in the repro its script is swapped inside the loop, and the fix swaps it after the loop.

AppImage, x86_64. Ubuntu 22.04, Debian 12, Fedora 39 or newer.

Intel or AMD Chromebook, with Linux turned on.

Download for macOSDownload for Windows

What Godot is checking

A for loop over an untyped value runs two opcodes: ITERATE_BEGIN for the first pass and ITERATE for every pass after it. ITERATE_BEGIN calls the value's iter_init and ITERATE calls iter_next; both then call iter_get to read the loop variable.

For an object, iter_init and iter_next call the object's _iter_init() and _iter_next() with a one-element array, and iter_get calls _iter_get() with the counter. If that _iter_get() call does not return CALL_OK, for example because the object no longer has the method, iter_get reports the value as invalid.

When iter_get fails on the first pass, the VM prints Unable to obtain iterator object of type 'Object'. When iter_next succeeded but iter_get failed on a later pass, it prints this message, with (but was obtained on first iteration?) added. The type is the Variant type name of the value being looped over, so a custom iterator always reads 'Object'. The check is inside DEBUG_ENABLED, so it runs in the editor and debug builds.

The function stops at the for line. In the repro the loop printed stop dock, the body replaced the object's script with Ticker, which has _iter_init() and _iter_next() but no _iter_get(), and the next pass failed: print("after") never ran, as the output shows.

Reproduce it

A Route class iterates its stops with _iter_init(), _iter_next() and _iter_get(). The loop body swaps the object's script for Ticker, which has no _iter_get(), after the first stop.

extends Node

class Route:
	var stops := ["dock", "mill", "farm"]

	func _iter_init(arg: Array) -> bool:
		arg[0] = 0
		return arg[0] < stops.size()

	func _iter_next(arg: Array) -> bool:
		arg[0] += 1
		return arg[0] < stops.size()

	func _iter_get(iter: Variant) -> Variant:
		return stops[iter]

class Ticker:
	var stops := ["dock", "mill", "farm"]

	func _iter_init(arg: Array) -> bool:
		arg[0] = 0
		return true

	func _iter_next(arg: Array) -> bool:
		arg[0] += 1
		return arg[0] < stops.size()

func _ready() -> void:
	var route = Route.new()
	for stop in route:
		print("stop ", stop)
		if stop == "dock":
			route.set_script(Ticker)
	print("after")

What Godot 4.7.2 prints

Godot Engine v4.7.2.stable.mono.official.ed1daf0bf - https://godotengine.org

stop dock
SCRIPT ERROR: Unable to obtain iterator object of type 'Object' (but was obtained on first iteration?).
   at: _ready (res://answers/unable-to-obtain-iterator-object-but-was-obtained-on-first-iteration/repro.gd:33)
   GDScript backtrace (most recent call first):
       [0] _ready (res://answers/unable-to-obtain-iterator-object-but-was-obtained-on-first-iteration/repro.gd:33)

Fix it

The loop only notes that a swap is wanted, and the script is replaced after the loop has ended, so every pass still reaches Route's _iter_get().

extends Node

class Route:
	var stops := ["dock", "mill", "farm"]

	func _iter_init(arg: Array) -> bool:
		arg[0] = 0
		return arg[0] < stops.size()

	func _iter_next(arg: Array) -> bool:
		arg[0] += 1
		return arg[0] < stops.size()

	func _iter_get(iter: Variant) -> Variant:
		return stops[iter]

class Ticker:
	var stops := ["dock", "mill", "farm"]

	func _iter_init(arg: Array) -> bool:
		arg[0] = 0
		return true

	func _iter_next(arg: Array) -> bool:
		arg[0] += 1
		return arg[0] < stops.size()

func _ready() -> void:
	var route = Route.new()
	var swap := false
	for stop in route:
		print("stop ", stop)
		if stop == "dock":
			swap = true
	if swap:
		route.set_script(Ticker)
	print("script swapped after the loop")

What the fixed script prints

Godot Engine v4.7.2.stable.mono.official.ed1daf0bf - https://godotengine.org

stop dock
stop mill
stop farm
script swapped after the loop

The object has no _iter_get() at all

If _iter_get() is missing from the start, the first pass fails and the VM prints the shorter message from ITERATE_BEGIN: Unable to obtain iterator object of type 'Object'. Add func _iter_get(iter: Variant) -> Variant that returns the value for the counter.

_iter_next() fails instead

If the object stops answering _iter_next(), or _iter_next() leaves the array it was given with a size other than one, iter_next reports the value as invalid first and the VM prints Unable to iterate on object of type 'Object' (type changed since first iteration?) instead.

Verified on Godot 4.7.2

Both scripts on this page were run headless on Godot 4.7.2 (4.7.2.stable.mono.official.ed1daf0bf) on 6 October 2026, and the output above is what Godot printed, unedited.

Where the check lives in Godot's source: [gdscript_vm.cpp lines 3446 to 3466, ITERATE and this message](https://github.com/godotengine/godot/blob/4.7.2-stable/modules/gdscript/gdscript_vm.cpp#L3446-L3466), [gdscript_vm.cpp lines 3040 to 3059, ITERATE_BEGIN and the first-pass messages](https://github.com/godotengine/godot/blob/4.7.2-stable/modules/gdscript/gdscript_vm.cpp#L3040-L3059), [variant_setget.cpp lines 1813 to 1818, iter_get calls _iter_get() and fails on a call error](https://github.com/godotengine/godot/blob/4.7.2-stable/core/variant/variant_setget.cpp#L1813-L1818), [variant_setget.cpp lines 1602 to 1630, iter_next calls _iter_next()](https://github.com/godotengine/godot/blob/4.7.2-stable/core/variant/variant_setget.cpp#L1602-L1630).

Questions

Why does the message say 'Object' and not my class name?
The VM builds it from Variant::get_type_name() of the looped value, which is the Variant type. Every object, whatever its class or script, is reported as 'Object'.