Day 5 · Concept 15
The event loop
JavaScript runs on a single thread. It executes synchronous code to completion, then drains the microtask queue (promises), then takes one macrotask (timer, I/O), then repeats. Blocking the loop means nothing else runs.
1 · The order of operations
console.log("1 sync");
setTimeout(() => console.log("4 macrotask"), 0);
Promise.resolve().then(() => console.log("3 microtask"));
console.log("2 sync");Synchronous code runs first. Then all queued microtasks (promise .thens).
Then one macrotask (setTimeout). Then repeat: microtasks again, etc.
2 · Microtask vs macrotask
| Microtask | Macrotask |
|---|---|
Promise .then/.catch/.finally | setTimeout, setInterval |
queueMicrotask | I/O callbacks (fs, net) |
| MutationObserver (browser) | setImmediate (Node) |
| Drained completely between each macrotask | One at a time |
The implication. A flood of resolved promises can starve macrotasks
(timers, I/O). Promise loops without yielding can lock up rendering in the browser.
3 · Blocking the loop
console.log("start");
setTimeout(() => console.log("timer"), 0);
// CPU-bound work — blocks everything
const t = Date.now();
while (Date.now() - t < 200) {}
console.log("end");The timer is delayed until the synchronous block finishes. In a browser, the page would freeze during the 200ms loop — no rendering, no input.
4 · Yielding — break up the work
// Bad — single CPU loop
function processAll(items) {
for (const item of items) heavyWork(item);
}
// Better — yield to the event loop between chunks
async function processAllChunked(items, chunkSize = 100) {
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
for (const item of chunk) heavyWork(item);
await new Promise(r => setTimeout(r, 0)); // yield
}
}
function heavyWork(x) { /* simulated CPU work */ }For pure CPU work in the browser, use Web Workers to move it off the main thread entirely. In Node, worker_threads serves the same purpose.
5 · Common mistakes
- Busy-waiting loops on the main thread.
- Tight promise chains that flood microtasks and starve macrotasks.
- Long synchronous JSON parse / stringify on the main thread. For huge payloads, do it in a worker.
- Recursive setTimeout intervals drift over time. Use
setIntervalif you need stable cadence (with care).
6 · When it clicks
- You predict the output order of mixed sync / promise / timeout code without running it.
- You know exactly why a 200-ms function freezes the page.
Web Worker/worker_threadsare reflex moves for CPU work.
Found this useful?