jestjs/jest · error · Error
Ran ${this._maxLoops} ticks, and there are still more! Assum
Error message
Ran ${this._maxLoops} ticks, and there are still more! Assuming we've hit an infinite recursion and bailing out... What it means
runAllTicks drains the `_ticks` queue (process.nextTick callbacks queued under legacy fake timers) for at most `_maxLoops` iterations (default 100,000). If the queue still is not empty after that, it assumes an infinite tick-recursion loop and throws at legacyFakeTimers.ts:170 to avoid hanging the process.
Source
Thrown at packages/jest-fake-timers/src/legacyFakeTimers.ts:170
let i;
for (i = 0; i < this._maxLoops; i++) {
const tick = this._ticks.shift();
if (tick === undefined) {
break;
}
if (
!Object.prototype.hasOwnProperty.call(this._cancelledTicks, tick.uuid)
) {
// Callback may throw, so update the map prior calling.
this._cancelledTicks[tick.uuid] = true;
tick.callback();
}
}
if (i === this._maxLoops) {
throw new Error(
`Ran ${this._maxLoops} ticks, and there are still more! ` +
"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);
}
View on GitHub (pinned to f49721c78e)
Solutions
- Find the callback that re-queues a tick and break the cycle (clear a flag, return early after first run).
- Prefer modern fake timers (`jest.useFakeTimers()` without `legacyFakeTimers: true`) which handle this more gracefully.
- Use `runOnlyPendingTimers`/bounded advances instead of unbounded `runAllTicks`.
- Raise `maxLoops` only after confirming the recursion is intentional and finite.
Example fix
// before
let pending = true;
process.nextTick(function cb() {
if (pending) { pending = false; process.nextTick(cb); }
});
jest.runAllTicks(); // infinite
// after
let count = 0;
process.nextTick(function cb() {
if (++count < 3) process.nextTick(cb);
});
jest.runOnlyPendingTimers(); Defensive patterns
Strategy: try-catch
Validate before calling
// cap tick recursion in the code under test
let n = 0;
process.nextTick(function cb() { if (++n < 100) process.nextTick(cb); }); Try / catch
try {
jest.runAllTicks();
} catch (e) {
if (!/infinite recursion/.test(String(e))) throw e;
// fall back to bounded draining
jest.runOnlyPendingTimers();
} Prevention
- Avoid recursive nextTick in production code.
- Prefer modern fake timers over legacy.
- Clear recursion flags before advancing.
When it happens
Trigger: Calling `jest.runAllTicks()` (legacy timers) while a `process.nextTick` callback re-queues another nextTick (or a callback that transitively schedules one), so each iteration adds work faster than it is drained.
Common situations: Mocked code uses `process.nextTick` recursively; a library (e.g. some readable-stream implementations) schedules follow-up ticks; switching from modern to legacy timers without adjusting the test; an event emitter that re-emits on nextTick.
Related errors
- Ran ${this._maxLoops} immediates, and there are still more!
- Ran ${this._maxLoops} timers, and there are still more! Assu
- `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/df2921930a4254d3.json.
Report an issue: GitHub.