phaserjs/phaser · error · Error
TimerEvent infinite loop created via zero delay
Error message
TimerEvent infinite loop created via zero delay
What it means
Thrown by Phaser.Time.Clock#addEvent when you re-add an existing TimerEvent instance whose delay is 0 (or negative) while it is configured to repeat. The guard prevents a timer that would fire every frame forever (repeatCount is 999999999999 for loop:true or repeat:-1), which would lock the game loop. The library treats zero-delay repeating timers as a programmer error rather than silently hanging.
Source
Thrown at src/time/Clock.js:216
* @return {Phaser.Time.TimerEvent} The Timer Event which was created, or passed in.
*/
addEvent: function (config)
{
var event;
if (config instanceof TimerEvent)
{
event = config;
this.removeEvent(event);
event.elapsed = event.startAt;
event.hasDispatched = false;
event.repeatCount = (event.repeat === -1 || event.loop) ? 999999999999 : event.repeat;
if (event.delay <= 0 && event.repeatCount > 0)
{
throw new Error('TimerEvent infinite loop created via zero delay');
}
}
else
{
event = new TimerEvent(config);
}
this._pendingInsertion.push(event);
return event;
},
/**
* Creates a Timer Event and adds it to the Clock at the start of the frame.
*
* This is a shortcut for {@link #addEvent} which can be shorter and is compatible with the syntax of the GreenSock Animation Platform (GSAP).
*
* @method Phaser.Time.Clock#delayedCallView on GitHub (pinned to 41be1e462b)
Solutions
- Set a positive delay (e.g. delay: 16 for ~60fps, or the frame time in ms) on the TimerEvent before calling addEvent.
- If you genuinely want a single immediate callback, set loop:false and repeat:0 (or omit them) so repeatCount is 0 and the guard is skipped.
- If you want a per-frame callback, use scene.events.on('preupdate', fn) or scene.sys.events instead of a zero-delay looping timer.
- If re-adding an existing instance, ensure its delay/startAt were not zeroed out by a prior reset.
Example fix
// before
this.time.addEvent(new Phaser.Time.TimerEvent({ delay: 0, loop: true, callback: tick }));
// after
this.time.addEvent(new Phaser.Time.TimerEvent({ delay: 16, loop: true, callback: tick })); Defensive patterns
Strategy: validation
Validate before calling
function safeReAddEvent(clock, event) {
var repeatCount = (event.repeat === -1 || event.loop) ? Infinity : event.repeat;
if (event.delay <= 0 && repeatCount > 0) {
event.delay = clock.scene.sys.game.loop.targetFps ? 1000 / clock.scene.sys.game.loop.targetFps : 16;
}
return clock.addEvent(event);
} Type guard
function isValidTimerEvent(event) {
var rc = (event.repeat === -1 || event.loop) ? 1 : event.repeat;
return event.delay > 0 || rc === 0;
} Try / catch
try {
this.time.addEvent(existingEvent);
} catch (e) {
if (e.message === 'TimerEvent infinite loop created via zero delay') {
existingEvent.delay = 16;
this.time.addEvent(existingEvent);
} else { throw e; }
} Prevention
- Never set delay:0 on a looping or repeating timer; default delay to a positive ms value when loop/repeat is set.
- Reuse TimerEvent instances only after resetting delay to a positive value.
- For per-frame work, prefer scene events ('preupdate'/'update') over zero-delay timers.
- Centralize timer creation behind a helper that validates delay>0 when repeat>0.
When it happens
Trigger: Calling this.time.addEvent(existingTimerEvent) where existingTimerEvent.delay <= 0 AND (existingTimerEvent.loop === true OR existingTimerEvent.repeat === -1 OR existingTimerEvent.repeat > 0). The branch only runs when the config argument is already a TimerEvent instance (not a plain config object); the same object passed as a config would instead hit TimerEvent.reset (error 41).
Common situations: Re-using/recycling a TimerEvent instance that was previously created with delay:0 and loop:true (common for a per-frame tick). Copy-pasting a config object into a TimerEvent via `new Phaser.Time.TimerEvent(config)` then passing it to addEvent. Migrating from delayedCall (which allows delay 0 for a one-shot) to a looping addEvent and forgetting to bump the delay.
Related errors
AI-assisted analysis of phaserjs/phaser@41be1e462b (2026-08-13).
Data as JSON: /api/errors/b615acadd3da3b2e.
Report an issue: GitHub.