15 / 20 · Day 5
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

js order.js
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

MicrotaskMacrotask
Promise .then/.catch/.finallysetTimeout, setInterval
queueMicrotaskI/O callbacks (fs, net)
MutationObserver (browser)setImmediate (Node)
Drained completely between each macrotaskOne 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

js block.js
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

js yield.js
// 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 setInterval if 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_threads are reflex moves for CPU work.
Found this useful?