jestjs/jest · error · Error
Ran ${this._maxLoops} immediates, and there are still more!
Error message
Ran ${this._maxLoops} immediates, and there are still more! Assuming we've hit an infinite recursion and bailing out... What it means
runAllImmediates drains the `_immediates` queue for up to `_maxLoops` iterations. If immediates are still pending after 100,000 (default) iterations, jest-fake-timers concludes a setImmediate callback is re-queueing itself and throws at legacyFakeTimers.ts:190 to break the cycle.
Source
Thrown at packages/jest-fake-timers/src/legacyFakeTimers.ts:190
"Assuming we've hit an infinite recursion and bailing out...",
);
}
}
runAllImmediates(): void {
this._checkFakeTimers();
// Only run a generous number of immediates and then bail.
let i;
for (i = 0; i < this._maxLoops; i++) {
const immediate = this._immediates.shift();
if (immediate === undefined) {
break;
}
this._runImmediate(immediate);
}
if (i === this._maxLoops) {
throw new Error(
`Ran ${this._maxLoops} immediates, and there are still more! Assuming ` +
"we've hit an infinite recursion and bailing out...",
);
}
}
private _runImmediate(immediate: Tick) {
try {
immediate.callback();
} finally {
this._fakeClearImmediate(immediate.uuid);
}
}
runAllTimers(): void {
this._checkFakeTimers();
this.runAllTicks();
this.runAllImmediates();View on GitHub (pinned to f49721c78e)
Solutions
- Break the re-queue loop with an explicit termination condition in the callback.
- Switch to modern fake timers, which provide `advanceTimersByTime` and do not require draining immediates separately.
- Use bounded draining (`runOnlyPendingTimers`) instead of `runAllImmediates`.
- Refactor the production code to avoid unbounded setImmediate polling.
Example fix
// before
setImmediate(function loop() {
doWork();
setImmediate(loop);
});
jest.runAllImmediates(); // infinite
// after
let keepGoing = true;
setImmediate(function loop() {
doWork();
if (keepGoing) setImmediate(loop);
});
keepGoing = false; // break before advancing
jest.runOnlyPendingTimers(); Defensive patterns
Strategy: try-catch
Validate before calling
// bound setImmediate recursion
let n = 0;
setImmediate(function loop() { if (++n < 50) setImmediate(loop); }); Try / catch
try {
jest.runAllImmediates();
} catch (e) {
if (!/infinite recursion/.test(String(e))) throw e;
jest.runOnlyPendingTimers();
} Prevention
- Terminate setImmediate poll loops with an explicit condition.
- Use modern timers where possible.
- Snapshot-drain with runOnlyPendingTimers instead of unbounded runAll.
When it happens
Trigger: Calling `jest.runAllImmediates()` (legacy timers) while a `setImmediate` callback calls `setImmediate` again, e.g. a poll loop or a queue drain that never empties.
Common situations: Mocked polling code; custom task schedulers built on setImmediate; RXJS/some stream libs that yield via setImmediate; legacy timer mode where the recursion guard of modern timers is absent.
Related errors
- Ran ${this._maxLoops} ticks, and there are still more! Assum
- Ran ${this._maxLoops} timers, and there are still more! Assu
- `jest.runAllImmediates()` is only available when using legac
- `jest.advanceTimersByTimeAsync()` is not available when usin
- `jest.advanceTimersToNextFrame()` is not available when usin
AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03).
Data as JSON: /data/errors/00dab8fd476d8a72.json.
Report an issue: GitHub.