You have 10,000 small jobs to run — parse rows, warm caches, build an index. Do them all in one loop and the main thread locks for a second: no scrolling, no clicks, a frozen page. The fix is time-slicing: run jobs in short bursts, and between bursts yield control back to the browser so it can paint a frame and handle input. This is exactly how React's scheduler and requestIdleCallback keep heavy work from janking the UI.
Implement taskSchedulerIdle(options) — a cooperative scheduler that runs a queue in time-budgeted slices, yielding between them. See MDN: requestIdleCallback.
function taskSchedulerIdle({ timeSlice, now, schedule, onDrain }) {
return { add, clear, size, isRunning };
}
const s = taskSchedulerIdle({ timeSlice: 5 });
for (const row of hugeList) s.add(() => index(row));
// runs ~5ms of tasks, yields to the browser, resumes next tick, ... until done
const cancel = s.add(() => expensive());
cancel(); // remove it if it hasn't run yet
now() - sliceStart < timeSlice; when the budget is spent, stop and schedule the next slice.schedule (requestIdleCallback/setTimeout in the browser) so the browser can paint and stay responsive.add returns a cancel; size/isRunning report state; onDrain fires once when the queue empties. Adding while running must not start a second loop.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.