Node's concurrency model is cooperative on a single main thread, backed by libuv thread pool and OS async I/O. Understanding the event loop explains why async code order surprises beginners and why blocking sync work hurts throughput.
Simplified phases
- Run synchronous code — top to bottom until the call stack is empty
- Microtasks — promise callbacks (
.then,awaitcontinuations) run before the next macrotask - Macrotasks —
setTimeout,setImmediate, I/O callbacks - Repeat — process pending work until the program exits
Classic ordering puzzle
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
// A, D, C, B
Sync first, then microtasks (C), then timer (B).
What to avoid
Long synchronous loops, huge JSON parsing on hot paths, or sync fs.readFileSync under load block the loop—every other request waits. Prefer async APIs and offload CPU work to worker threads when needed.
Important interview questions and answers
- Q: Is Node multi-threaded?
A: JavaScript runs on one main thread; libuv and worker threads handle some I/O and CPU work behind the scenes—the model feels single-threaded to your JS code. - Q: Microtask vs macrotask?
A: Promises run as microtasks before the next timer/I/O macrotask—explains whyawaitresolves beforesetTimeout(fn, 0). - Q: When use worker_threads?
A: CPU-intensive tasks (image processing, heavy crypto) that would starve the event loop on the main thread.
Self-check
- In the puzzle above, why does C print before B?
- What happens if you run a 30-second sync loop on the main thread during a request?
Tip: Draw the event loop as a queue: synchronous code runs first, then microtasks (promises), then macrotasks (timers). Blocking the main thread blocks everything.
Interview prep
- What blocks the Node event loop?
Synchronous CPU-heavy work, long loops, and blocking sync I/O on the main thread—use worker threads, queues, or async APIs instead.