Quick answer: When you destroy an object in Construct 3, the Selected Object List (SOL) still holds a reference to it for the remainder of the event block. Subsequent conditions or actions that try to pick or read properties from that instance will fail. Fix this by using “For Each” loops, moving post-destroy logic to separate events, or using the “Wait 0 seconds” trick to defer actions until the next tick.
Here is how to fix the Construct 3 “Object Not Found” error after destroying instances. You destroy an enemy, a bullet, or a collectible — and then somewhere later in the same event sheet, Construct throws an error because it tried to reference an object that no longer exists. This is one of the most common Construct 3 bugs, and it comes down to how the engine handles object picking and destruction timing.
The Symptom
You have an event that destroys an instance — for example, a bullet hitting an enemy. After the destroy action, another event in the same tick tries to access the same object type. The console shows an error like “Cannot read properties of undefined” or the event simply does not execute. In some cases, the game appears to freeze momentarily or skip an entire block of logic.
This typically appears when you have multiple events that reference the same object type. The first event destroys an instance, and a later event (or even a sub-event in the same block) tries to pick from the same object type. The destroyed instance is gone, but the SOL still thinks it exists.
You might also see this when using “Pick all” or “Pick by comparison” after a destroy. The picked set includes the now-invalid reference, and any action applied to it fails.
What Causes This
There are four common causes:
1. SOL corruption after destroy. Construct 3 uses the Selected Object List (SOL) to track which instances are currently picked by conditions. When you destroy an instance, it is marked for removal but not immediately deleted. The SOL may still contain a reference to the destroyed instance. If a subsequent condition in the same top-level event group tries to access the object, it encounters a stale reference.
2. Accessing destroyed objects in sub-events. If you destroy an object in a parent event and then have sub-events that reference the same object type, those sub-events inherit the parent’s SOL — which now contains an invalid reference. This is especially common in event structures with multiple levels of nesting.
3. For Each not used when destroying multiple instances. When you pick multiple instances and destroy them in a single action, Construct destroys all picked instances. If you then try to iterate or access other instances of the same type in a subsequent event, the SOL state can be inconsistent. Without a “For Each” loop, the engine does not properly isolate each instance’s lifecycle.
4. Storing instance references that become stale. If you store a reference to an instance in an instance variable or a global variable (via UID), and then destroy the instance, any later lookup by that UID will fail. The UID becomes invalid the moment the instance is destroyed.
The Fix
Step 1: Use “For Each” to safely iterate and destroy. When you need to destroy instances based on a condition, wrap the logic in a “For Each” loop. This ensures each instance is individually picked and destroyed without corrupting the SOL for other instances.
// Event sheet: Safely destroy enemies at 0 health
+ System: For Each Enemy
+ Enemy: health ≤ 0
-> Enemy: Spawn ExplosionEffect on layer 1 at (Enemy.X, Enemy.Y)
-> Enemy: Destroy
Without the “For Each”, destroying one enemy can invalidate the SOL for subsequent enemies in the same pick. The loop isolates each iteration.
Step 2: Move post-destroy logic to a separate event. Do not try to read properties of an object after destroying it in the same event block. Instead, save what you need first, or put the follow-up logic in a different top-level event.
// BAD: Accessing Enemy after Destroy in the same block
+ Bullet: On collision with Enemy
-> Enemy: Destroy
-> ScoreText: Set text to Enemy.ScoreValue // ERROR: Enemy is destroyed
// GOOD: Save the value before destroying
+ Bullet: On collision with Enemy
-> System: Set local variable "points" to Enemy.ScoreValue
-> Enemy: Destroy
-> ScoreText: Set text to local.points
Step 3: Use “Wait 0 seconds” to defer post-destroy actions. The Wait action with a duration of 0 yields execution until the next tick. By that point, the destroyed instance is fully cleaned up and the SOL has been reset.
// Using Wait 0 to safely run logic after destruction
+ Bullet: On collision with Enemy
-> Enemy: Destroy
-> System: Wait 0 seconds
-> System: Set EnemyCount to Enemy.Count // Safe: SOL is fresh
Note that “Wait 0” converts the rest of the event into an asynchronous action. Any actions after the Wait will run one frame later. This is fine for non-time-critical logic like updating UI counters, but avoid it for physics-sensitive operations.
Step 4: Use the scripting API for complex cases. If you need finer control over instance lifecycle, the JavaScript scripting API lets you check whether an instance still exists before accessing it.
// JavaScript: Check if instance is still valid before access
const enemy = runtime.objects.Enemy.getFirstPickedInstance();
if (enemy && !enemy.isDestroyed) {
const points = enemy.instVars.ScoreValue;
enemy.destroy();
runtime.globalVars.Score += points;
}
Why This Works
Each fix addresses the core problem: the timing gap between when you call Destroy and when the instance is actually removed from the engine.
For Each isolation works because the loop processes one instance at a time. When an instance is destroyed inside the loop, the iterator moves to the next valid instance. The SOL for each iteration only contains the current instance, so destroying it does not corrupt references to other instances.
Saving values before destroy is the most straightforward fix. Once you copy the data you need into a variable, you have no dependency on the instance. The destroy can happen at any point after the copy without affecting your logic.
Wait 0 forces a tick boundary. Construct 3 processes all pending destructions between ticks. By the time your post-Wait actions execute, the destroyed instances are fully cleaned up, the SOL has been reset, and any “Pick all” or “Pick by comparison” will only return instances that still exist.
The scripting API provides explicit lifecycle checks. The isDestroyed property is a reliable guard that does not depend on SOL state, giving you imperative control over when and how you access instance data.
"The SOL is the single most important concept to understand in Construct 3 event sheets. Once you internalize how picking and destruction interact, half of all Construct 3 bugs disappear."
Related Issues
If your game shows a black screen when switching layouts instead of an object reference error, see our guide on Construct 3 layout not loading / black screen on transition. For issues where animations stop working after object manipulation, check Construct 3 animation not playing or stuck on first frame. And if your collision events are not firing at all, Construct 3 collision not detected / overlap not triggering covers the common causes.
Save first, destroy second. Never the other way around.