{"record":{"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/8e6d128e4a278059ecddecaa97400b04c8ae5fd9/packages/jest-fake-timers/src/legacyFakeTimers.ts#L220-L256","documentation":"Thrown by legacy fake timers' `runAllTimers()` after exhausting `_maxLoops` iterations (default 100,000). `runAllTimers` drains ticks, immediates, and timers in a loop; if a timer callback schedules another timer (especially `setInterval`), the timer map never empties and Jest aborts.","triggerScenarios":"Calling `jest.runAllTimers()` with legacy fake timers active when code has an active `setInterval` or a `setTimeout` that recursively schedules itself. Each loop iteration runs one timer expiry, but the callback re-enqueues, so the loop counter hits the cap.","commonSituations":"An unmocked polling interval. A `setTimeout` retry loop. Code that uses recursive setTimeout for animation frames or scheduling. Forgetting to `clearInterval` before the test ends.","solutions":["Use `jest.runOnlyPendingTimers()` instead — it runs only currently scheduled timers without recursing into new ones.","Use `jest.advanceTimersByTime(ms)` to advance a bounded amount.","Switch to modern fake timers for better control.","Ensure `setInterval` calls are cleared with `clearInterval` in the test or application code."],"exampleFix":"// before\njest.useFakeTimers({ legacyFakeTimers: true });\nsetInterval(() => {}, 100);\njest.runAllTimers(); // throws — interval never ends\n\n// after\njest.useFakeTimers({ legacyFakeTimers: true });\nconst id = setInterval(() => {}, 100);\njest.runOnlyPendingTimers(); // safe\nclearInterval(id);","handlingStrategy":"validation","validationCode":"// Detect active intervals before runAllTimers\nfunction hasActiveInterval(jestObj) {\n  // Use getTimerCount as a heuristic; high count + intervals = risk\n  return jestObj.getTimerCount() > 1000;\n}","typeGuard":null,"tryCatchPattern":"try {\n  jest.runAllTimers();\n} catch (e) {\n  if (e.message.includes('infinite recursion')) {\n    console.warn('Switching to runOnlyPendingTimers due to recursive timers');\n    jest.runOnlyPendingTimers();\n  } else { throw e; }\n}","preventionTips":["Never call runAllTimers when setInterval is active — use runOnlyPendingTimers.","Clear intervals in beforeEach/afterEach to prevent cross-test contamination.","Switch to modern fake timers which handle intervals better.","Audit test code for unmocked polling loops."],"tags":["jest-fake-timers","legacy-fake-timers","infinite-loop","setinterval","settimeout"],"backgroundTag":null,"analyzedSha":"8e6d128e4a278059ecddecaa97400b04c8ae5fd9","analyzedAt":"2026-08-10T18:11:27.960Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}