jestjs/jest · error · Error
Ran timers, and there are still more! Assuming we've hit an…
Error message
Ran ${this._maxLoops} timers, and there are still more! Assuming we've hit an infinite recursion and bailing out... What it means
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.
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.
Example fix
// before
jest.useFakeTimers({ legacyFakeTimers: true });
setInterval(() => {}, 100);
jest.runAllTimers(); // throws — interval never ends
// after
jest.useFakeTimers({ legacyFakeTimers: true });
const id = setInterval(() => {}, 100);
jest.runOnlyPendingTimers(); // safe
clearInterval(id); Defensive patterns
Strategy: validation
Validate before calling
// Detect active intervals before runAllTimers
function hasActiveInterval(jestObj) {
// Use getTimerCount as a heuristic; high count + intervals = risk
return jestObj.getTimerCount() > 1000;
} Try / catch
try {
jest.runAllTimers();
} catch (e) {
if (e.message.includes('infinite recursion')) {
console.warn('Switching to runOnlyPendingTimers due to recursive timers');
jest.runOnlyPendingTimers();
} else { throw e; }
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Ran immediates, and there are still more! Assuming we've…
- Ran ticks, and there are still more! Assuming we've hit an…
AI-assisted analysis of jestjs/jest@8e6d128e4a (2026-08-10).
Data as JSON: /api/errors/0e32c4d0269da90a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/jest-fake-timers/src/legacyFakeTimers.ts:238
const [nextTimerHandle, expiry] = nextTimerHandleAndExpiry;
this._now = expiry;
this._runTimerHandle(nextTimerHandle);
// Some of the immediate calls could be enqueued
// during the previous handling of the timers, we should
// run them as well.
if (this._immediates.length > 0) {
this.runAllImmediates();
}
if (this._ticks.length > 0) {
this.runAllTicks();
}
}
if (i === this._maxLoops) {
throw new Error(
`Ran ${this._maxLoops} timers, and there are still more! ` +
"Assuming we've hit an infinite recursion and bailing out...",
);
}
}
runOnlyPendingTimers(): void {
// We need to hold the current shape of `this._timers` because existing
// timers can add new ones to the map and hence would run more than necessary.
// See https://github.com/jestjs/jest/pull/4608 for details
const timerEntries = [...this._timers.entries()];
this._checkFakeTimers();
for (const _immediate of this._immediates) this._runImmediate(_immediate);
for (const [timerHandle, timer] of timerEntries.sort(
([, left], [, right]) => left.expiry - right.expiry,
)) {
this._now = timer.expiry;View on GitHub (pinned to 8e6d128e4a)