phaserjs/phaser · error · Error
TimerEvent infinite loop created via zero delay
Error message
TimerEvent infinite loop created via zero delay
What it means
Thrown inside Phaser.Time.TimerEvent#reset (also invoked by the constructor) when delay is 0 or negative while repeatCount is positive. repeatCount becomes 999999999999 when repeat is -1 or loop is true, otherwise equals repeat. The check is the constructor's safety net: a zero-delay repeating event would dispatch every frame indefinitely, so Phaser refuses to build it. This is the same invariant as error 40 but reached via the plain-config path.
Source
Thrown at src/time/TimerEvent.js:193
this.callback = GetFastValue(config, 'callback', undefined);
this.callbackScope = GetFastValue(config, 'callbackScope', this);
this.args = GetFastValue(config, 'args', []);
this.timeScale = GetFastValue(config, 'timeScale', 1);
this.startAt = GetFastValue(config, 'startAt', 0);
this.paused = GetFastValue(config, 'paused', false);
this.elapsed = this.startAt;
this.hasDispatched = false;
this.repeatCount = (this.repeat === -1 || this.loop) ? 999999999999 : this.repeat;
if (this.delay <= 0 && this.repeatCount > 0)
{
throw new Error('TimerEvent infinite loop created via zero delay');
}
return this;
},
/**
* Gets the progress of the current iteration, not factoring in repeats.
*
* @method Phaser.Time.TimerEvent#getProgress
* @since 3.0.0
*
* @return {number} A number between 0 and 1 representing the current progress.
*/
getProgress: function ()
{
return (this.elapsed / this.delay);
},
View on GitHub (pinned to 41be1e462b)
Solutions
- Set delay to a positive integer of milliseconds (e.g. 1000 for 1 second) whenever loop or repeat is set.
- If you want exactly one fire, omit loop/repeat (both default so repeatCount is 0) instead of setting repeat: 1 with delay: 0.
- Validate loaded config: `if (cfg.loop || cfg.repeat) { cfg.delay = cfg.delay || 1000; }`.
- Use scene.time.delayedCall(delay, cb) for single-shot calls, which has a clearer contract.
Example fix
// before
new Phaser.Time.TimerEvent({ delay: 0, loop: true, callback: tick });
// after
new Phaser.Time.TimerEvent({ delay: 1000, loop: true, callback: tick }); Defensive patterns
Strategy: validation
Validate before calling
function safeTimerConfig(cfg) {
var rc = (cfg.repeat === -1 || cfg.loop) ? 1 : (cfg.repeat || 0);
if ((cfg.delay === undefined || cfg.delay <= 0) && rc > 0) {
cfg.delay = 1000; // sane default
}
return cfg;
}
// usage: new Phaser.Time.TimerEvent(safeTimerConfig(config)) Type guard
function isValidTimerConfig(cfg) {
var rc = (cfg.repeat === -1 || cfg.loop) ? 1 : (cfg.repeat || 0);
return (cfg.delay !== undefined && cfg.delay > 0) || rc === 0;
} Try / catch
try {
event.reset(config);
} catch (e) {
if (e.message === 'TimerEvent infinite loop created via zero delay') {
config.delay = 1000;
event.reset(config);
} else { throw e; }
} Prevention
- Always specify delay in milliseconds whenever loop or repeat is set.
- Treat delay:0 as 'uninitialized' and replace it with a sensible default in config builders.
- Validate external/data-driven configs before passing to TimerEvent.
- Prefer delayedCall for single-shot callbacks to avoid the repeat/delay interaction.
When it happens
Trigger: Constructing `new Phaser.Time.TimerEvent({ delay: 0, repeat: 1 })`, or `{ delay: 0, loop: true }`, or `{ delay: 0, repeat: -1 }`. Also fires from Clock.addEvent when you pass a plain config object (not a TimerEvent instance) with those values, since addEvent routes config objects through `new TimerEvent(config)`. Also from calling event.reset(config) on an existing event with the same bad combination.
Common situations: Forgetting the delay field (GetFastValue defaults delay to 0) while setting loop:true or repeat:N. Reading delay from a data file or server where 0 is used as 'not set'. Copying a one-shot delayedCall config and adding loop:true without re-checking delay. Treating delay as seconds instead of milliseconds (delay: 0.5 reads as a positive number but a near-zero ms delay still works; the trap is delay: 0 exactly).
Related errors
- TimerEvent infinite loop created via zero delay
- Must set explicit renderType in custom environment
- Unknown value for renderer type:
- No DOM Container set in game config
- Invalid File type:
AI-assisted analysis of phaserjs/phaser@41be1e462b (2026-08-13).
Data as JSON: /api/errors/99d214585d2456b7.
Report an issue: GitHub.