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

  1. Use `jest.runOnlyPendingTimers()` instead — it runs only currently scheduled timers without recursing into new ones.
  2. Use `jest.advanceTimersByTime(ms)` to advance a bounded amount.
  3. Switch to modern fake timers for better control.
  4. 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

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


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)