๐ŸŽฎ Give the browser a break

The same CPU work, run three ways. The blue dot is driven by requestAnimationFrame, so it only moves when the browser gets a turn โ€” if it stops, your thread is blocked. Run each mode and watch the dot, then try clicking the button mid-run.

1 Pick a mode and run

Workload is calibrated to real CPU time on your machine, so 600 ms means roughly 600 ms of actual arithmetic. The yield budget only affects the two yielding modes โ€” it is the > 50 in if (performance.now() - lastYield > 50).

2 Is the browser still alive?
idle
0 clicks ยท worst delay โ€”
frames painted โ€”
longest freeze โ€”
total time โ€”
Ready โ€” pick a mode above.

โ€œLongest freezeโ€ is the biggest gap between two animation frames. Anything over ~50 ms is what Chrome DevTools flags as a long task; over ~200 ms and users assume your app is broken.

3 Compare your runs
modeframeslongest freezeclicks heardtotal

Run all three with the same workload. Blocking is the one that should look obviously broken: a single frame and one enormous freeze. Both yielding modes keep the worst freeze near your budget instead. Between those two, expect scheduler.yield() to finish sooner โ€” browsers clamp nested setTimeout to roughly 4 ms per hop, and across hundreds of hops that adds up, while a yield continuation resumes in a boosted task queue. setTimeout(0) may well paint more frames along the way; it is buying them with wall-clock time you did not choose to spend.

4 The loop that is running

The highlighted line is the only difference between the modes.