{"record":{"id":"99d214585d2456b7","repo":"phaserjs/phaser","slug":"timerevent-infinite-loop-created-via-zero-delay-99d214","errorCode":null,"errorMessage":"TimerEvent infinite loop created via zero delay","messagePattern":"TimerEvent infinite loop created via zero delay","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/time/TimerEvent.js","lineNumber":193,"sourceCode":"        this.callback = GetFastValue(config, 'callback', undefined);\n\n        this.callbackScope = GetFastValue(config, 'callbackScope', this);\n\n        this.args = GetFastValue(config, 'args', []);\n\n        this.timeScale = GetFastValue(config, 'timeScale', 1);\n\n        this.startAt = GetFastValue(config, 'startAt', 0);\n\n        this.paused = GetFastValue(config, 'paused', false);\n\n        this.elapsed = this.startAt;\n        this.hasDispatched = false;\n        this.repeatCount = (this.repeat === -1 || this.loop) ? 999999999999 : this.repeat;\n\n        if (this.delay <= 0 && this.repeatCount > 0)\n        {\n            throw new Error('TimerEvent infinite loop created via zero delay');\n        }\n\n        return this;\n    },\n\n    /**\n     * Gets the progress of the current iteration, not factoring in repeats.\n     *\n     * @method Phaser.Time.TimerEvent#getProgress\n     * @since 3.0.0\n     *\n     * @return {number} A number between 0 and 1 representing the current progress.\n     */\n    getProgress: function ()\n    {\n        return (this.elapsed / this.delay);\n    },\n","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/phaserjs/phaser/blob/41be1e462bc600064e498cba370bfa8c5c055a22/src/time/TimerEvent.js#L175-L211","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before\nnew Phaser.Time.TimerEvent({ delay: 0, loop: true, callback: tick });\n\n// after\nnew Phaser.Time.TimerEvent({ delay: 1000, loop: true, callback: tick });","handlingStrategy":"validation","validationCode":"function safeTimerConfig(cfg) {\n  var rc = (cfg.repeat === -1 || cfg.loop) ? 1 : (cfg.repeat || 0);\n  if ((cfg.delay === undefined || cfg.delay <= 0) && rc > 0) {\n    cfg.delay = 1000; // sane default\n  }\n  return cfg;\n}\n// usage: new Phaser.Time.TimerEvent(safeTimerConfig(config))","typeGuard":"function isValidTimerConfig(cfg) {\n  var rc = (cfg.repeat === -1 || cfg.loop) ? 1 : (cfg.repeat || 0);\n  return (cfg.delay !== undefined && cfg.delay > 0) || rc === 0;\n}","tryCatchPattern":"try {\n  event.reset(config);\n} catch (e) {\n  if (e.message === 'TimerEvent infinite loop created via zero delay') {\n    config.delay = 1000;\n    event.reset(config);\n  } else { throw e; }\n}","preventionTips":["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."],"tags":["timer","timer-event","infinite-loop","config","time"],"backgroundTag":null,"analyzedSha":"41be1e462bc600064e498cba370bfa8c5c055a22","analyzedAt":"2026-08-13T04:23:39.729Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}