jestjs/jest · error · Error
Ran ${this._maxLoops} timers, and there are still more! Assu
Error message
Ran ${this._maxLoops} timers, and there are still more! Assuming we've hit an infinite recursion and bailing out... What it means
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.
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 f49721c78e)
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.
Example fix
// before setInterval(() => poll(), 100); jest.runAllTimers(); // infinite interval // after const id = setInterval(() => poll(), 100); jest.runOnlyPendingTimers(); // runs the snapshot once clearInterval(id);
Defensive patterns
Strategy: try-catch
Validate before calling
// clear intervals before draining timers if (intervalId) clearInterval(intervalId); jest.runAllTimers();
Try / catch
try {
jest.runAllTimers();
} catch (e) {
if (!/infinite recursion/.test(String(e))) throw e;
jest.runOnlyPendingTimers();
} Prevention
- Clear intervals in afterEach.
- Use runOnlyPendingTimers for intervals.
- Switch to modern timers.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Ran ${this._maxLoops} ticks, and there are still more! Assum
- Ran ${this._maxLoops} immediates, and there are still more!
- `jest.advanceTimersByTimeAsync()` is not available when usin
- `jest.advanceTimersToNextFrame()` is not available when usin
- `jest.advanceTimersToNextTimerAsync()` is not available when
AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03).
Data as JSON: /data/errors/0e32c4d0269da90a.json.
Report an issue: GitHub.