jestjs/jest · error · Error
Ran ticks, and there are still more! Assuming we've hit an…
Error message
Ran ${this._maxLoops} ticks, and there are still more! Assuming we've hit an infinite recursion and bailing out... What it means
Thrown by legacy fake timers' `runAllTicks()` after exhausting `_maxLoops` iterations (default 100,000). The method drains `process.nextTick` callbacks; if each tick schedules another tick, the queue never empties and Jest bails out to avoid a true infinite loop.
Solutions
- Switch from legacy to modern fake timers: `jest.useFakeTimers()` (without `legacyFakeTimers: true`) — sinon-backed timers handle this more gracefully.
- Use `jest.runOnlyPendingTimers()` or `jest.advanceTimersByTime(n)` instead of `runAllTicks()` to bound execution.
- Fix the application code to have a termination condition for the nextTick chain.
- Increase `maxLoops` via the FakeTimers constructor option if the loop is legitimately large but finite.
Example fix
// before
jest.useFakeTimers({ legacyFakeTimers: true });
// app code: function poll() { process.nextTick(poll); }
poll();
jest.runAllTicks(); // throws
// after: use modern timers + bounded advance
jest.useFakeTimers();
poll();
jest.advanceTimersByTime(1000); // bounded Defensive patterns
Strategy: validation
Validate before calling
// Detect recursive nextTick before running all ticks
function isRecursiveNextTick(global) {
// heuristic: check if tick queue grows after running one tick
const origNextTick = global.process.nextTick;
let tickCount = 0;
global.process.nextTick = function(cb) { tickCount++; origNextTick(cb); };
return tickCount;
} Try / catch
// Wrap timer operations to catch infinite-loop bailouts
try {
jest.runAllTicks();
} catch (e) {
if (e.message.includes('infinite recursion')) {
console.warn('Recursive nextTick detected — use runOnlyPendingTimers or modern timers');
}
throw e;
} Prevention
- Prefer modern fake timers over legacy for recursive scheduling scenarios.
- Use runOnlyPendingTimers instead of runAllTicks for potentially recursive code.
- Ensure application code has termination conditions for nextTick chains.
- Increase maxLoops only as a last resort after confirming the loop is finite.
When it happens
Trigger: Using `jest.useFakeTimers({ legacyFakeTimers: true })` and calling `jest.runAllTicks()` when application code recursively calls `process.nextTick` inside a nextTick callback. Each iteration shifts a tick and runs it, but the callback enqueues another, so the counter never reaches zero.
Common situations: Code that uses `process.nextTick` for a polling/retry loop without a termination condition. A recursive nextTick chain meant to yield control but never stops. Testing code with a real event-emitter that re-schedules on every tick.
Related errors
- Ran immediates, and there are still more! Assuming we've…
- Ran timers, 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/df2921930a4254d3.
Report an issue: GitHub.
Appendix: 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 8e6d128e4a)