iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Event Loop

Node's event loop is what schedules every async callback — including the microtasks that resolve promises. Knowing the order (sync → microtask queue → macrotask queue) makes async bugs predictable.

Microtask vs macrotask order, await scheduling

EXAMPLE
// 1) The big picture
// Every Node tick runs in this order:
//   a) Synchronous code in the current call stack
//   b) MICROtasks — Promise callbacks, queueMicrotask
//   c) Timers (setTimeout, setInterval) — when their delay is up
//   d) I/O callbacks — fs, net, http responses
//   e) setImmediate callbacks
//   f) Close events (socket close, etc.)
// Microtasks drain between EACH macrotask phase.

console.log('1 sync');

setTimeout(() => console.log('5 timeout'), 0);
setImmediate(() => console.log('6 immediate'));
process.nextTick(() => console.log('3 nextTick'));     // even earlier than promises
Promise.resolve().then(() => console.log('4 promise'));

queueMicrotask(() => console.log('4b microtask'));

console.log('2 sync');

// Output:
//   1 sync
//   2 sync
//   3 nextTick
//   4 promise
//   4b microtask
//   5 timeout      (or 6 immediate, depending on phase)
//   6 immediate

// 2) await yields the rest of the function to a microtask
async function run() {
    console.log('A');
    await Promise.resolve();    // suspends here, resumes as a microtask
    console.log('C');
}
run();
console.log('B');
// → A, B, C

// 3) Common promise patterns
await Promise.all([fetchUser(id), fetchOrders(id), fetchPosts(id)]);
await Promise.allSettled(urls.map(u => fetch(u)));

async function retry(fn, { attempts = 3, base = 200 } = {}) {
    let last;
    for (let i = 0; i < attempts; i++) {
        try { return await fn(); }
        catch (e) {
            last = e;
            await new Promise(r => setTimeout(r, base * 2 ** i));
        }
    }
    throw last;
}

// 4) NEVER block the event loop
// Bad — sync CPU work freezes everything else
const hash = crypto.pbkdf2Sync(pw, salt, 600_000, 32, 'sha256');

// Good — async + worker_threads if it's heavy
const hashAsync = await new Promise((res, rej) =>
    crypto.pbkdf2(pw, salt, 600_000, 32, 'sha256', (e, k) => e ? rej(e) : res(k)));

Why it matters

Node is single-threaded by design. A 100 ms sync CPU loop holds up EVERY request. Know which APIs are sync (the *Sync ones) and reach for the async / worker_threads version on hot paths.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
console.log('1');
setTimeout(() => console.log('3'), 0);
Promise.resolve().then(() => console.log('2'));
// Output: 1, 2, 3 — microtasks before macrotasks.
Try it Yourself »

Exercise

Promise callbacks are microtasks; setTimeout callbacks are…

tasks

Test yourself

Q1. Microtasks run…
Q2. setTimeout(fn, 0) is a…
Q3. Process exits when…

Discussion

Loading…