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

  1. Set delay to a positive integer of milliseconds (e.g. 1000 for 1 second) whenever loop or repeat is set.
  2. If you want exactly one fire, omit loop/repeat (both default so repeatCount is 0) instead of setting repeat: 1 with delay: 0.
  3. Validate loaded config: `if (cfg.loop || cfg.repeat) { cfg.delay = cfg.delay || 1000; }`.
  4. 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

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


AI-assisted analysis of phaserjs/phaser@41be1e462b (2026-08-13). Data as JSON: /api/errors/99d214585d2456b7. Report an issue: GitHub.