{"id":"0e32c4d0269da90a","repo":"jestjs/jest","slug":"ran-this-maxloops-timers-and-there-are-still","errorCode":null,"errorMessage":"Ran ${this._maxLoops} timers, and there are still more! Assuming we've hit an infinite recursion and bailing out...","messagePattern":"Ran (.+?) timers, and there are still more! Assuming we've hit an infinite recursion and bailing out\\.\\.\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/jest-fake-timers/src/legacyFakeTimers.ts","lineNumber":238,"sourceCode":"\n      const [nextTimerHandle, expiry] = nextTimerHandleAndExpiry;\n      this._now = expiry;\n      this._runTimerHandle(nextTimerHandle);\n\n      // Some of the immediate calls could be enqueued\n      // during the previous handling of the timers, we should\n      // run them as well.\n      if (this._immediates.length > 0) {\n        this.runAllImmediates();\n      }\n\n      if (this._ticks.length > 0) {\n        this.runAllTicks();\n      }\n    }\n\n    if (i === this._maxLoops) {\n      throw new Error(\n        `Ran ${this._maxLoops} timers, and there are still more! ` +\n          \"Assuming we've hit an infinite recursion and bailing out...\",\n      );\n    }\n  }\n\n  runOnlyPendingTimers(): void {\n    // We need to hold the current shape of `this._timers` because existing\n    // timers can add new ones to the map and hence would run more than necessary.\n    // See https://github.com/jestjs/jest/pull/4608 for details\n    const timerEntries = [...this._timers.entries()];\n    this._checkFakeTimers();\n    for (const _immediate of this._immediates) this._runImmediate(_immediate);\n\n    for (const [timerHandle, timer] of timerEntries.sort(\n      ([, left], [, right]) => left.expiry - right.expiry,\n    )) {\n      this._now = timer.expiry;","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/jestjs/jest/blob/f49721c78e195558b40913977c9230f5b7f559d8/packages/jest-fake-timers/src/legacyFakeTimers.ts#L220-L256","documentation":"runAllTimers drains ticks, immediates, and then the timer map repeatedly for up to `_maxLoops` iterations. Each iteration runs the soonest timer; if new timers keep being scheduled (e.g. an interval or a setTimeout that reschedules itself), the loop never converges, so jest throws at legacyFakeTimers.ts:238 to avoid an infinite hang.","triggerScenarios":"Calling `jest.runAllTimers()` (legacy timers) when an active `setInterval` exists, or when a `setTimeout` callback schedules another `setTimeout`. Each fired timer adds a successor so the loop runs forever.","commonSituations":"An interval was set up in beforeEach and not cleared; mocked animation frames via setInterval; a retry-with-backoff implemented as recursive setTimeout; legacy fake timers do not auto-detect convergence.","solutions":["Clear the interval/recursive timeout before running all timers, or use `runOnlyPendingTimers()` which snapshots the current set.","Switch to modern fake timers and use `advanceTimersByTime(ms)` for a bounded advance.","If you genuinely need an interval, assert on a fixed number of ticks: `for (let i=0;i<5;i++) jest.advanceTimersByTime(interval)`.","Refactor the code to make the recursion terminating."],"exampleFix":"// before\nsetInterval(() => poll(), 100);\njest.runAllTimers(); // infinite interval\n\n// after\nconst id = setInterval(() => poll(), 100);\njest.runOnlyPendingTimers(); // runs the snapshot once\nclearInterval(id);","handlingStrategy":"try-catch","validationCode":"// clear intervals before draining timers\nif (intervalId) clearInterval(intervalId);\njest.runAllTimers();","typeGuard":null,"tryCatchPattern":"try {\n  jest.runAllTimers();\n} catch (e) {\n  if (!/infinite recursion/.test(String(e))) throw e;\n  jest.runOnlyPendingTimers();\n}","preventionTips":["Clear intervals in afterEach.","Use runOnlyPendingTimers for intervals.","Switch to modern timers."],"tags":["jest-fake-timers","legacy","infinite-loop","setinterval"],"analyzedSha":"f49721c78e195558b40913977c9230f5b7f559d8","analyzedAt":"2026-08-03T20:16:28.571Z","schemaVersion":2}