You will be able to explain how JavaScript runs asynchronous code on one thread by using the event loop, microtasks, and macrotasks.
What it is
JavaScript runs on a single main thread. That means only one piece of JavaScript can execute at a time. The event loop is the mechanism that lets JavaScript handle asynchronous work without blocking that thread.
A useful mental model is:
- The call stack runs synchronous code.
- Browser or Node APIs handle asynchronous work outside the main thread.
- When that work finishes, a callback is placed in a queue.
- The event loop moves callbacks from queues to the call stack when the stack is empty.
There are two important queues:
- Microtasks: small, high-priority tasks such as
Promise.then()callbacks andasync/awaitcontinuations. - Macrotasks: larger tasks such as
setTimeout(),setInterval(), I/O callbacks, and user events.
Related terms include call stack, task queue, microtask queue, Promise, and async/await.
Why it matters
- It explains why long synchronous loops can freeze a browser page.
- It helps you predict the order of asynchronous callbacks.
- It shows why
Promise.then()can run beforesetTimeout(). - It helps you choose the right scheduling tool for responsive UI code.
- It prevents mistakes like accidentally starving the event loop with recursive microtasks.
Syntax or steps
The smallest useful pattern is to understand the order of execution:
- Run all synchronous code first.
- When the call stack is empty, run all available microtasks.
- Run one macrotask.
- Run all microtasks again.
- Repeat.
For example, schedule a microtask with:
Promise.resolve().then(() => {
console.log('microtask');
});
Schedule a macrotask with:
setTimeout(() => {
console.log('macrotask');
}, 0);
Example
console.log('start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');
Expected output:
start
end
promise
timeout
Part-by-part explanation:
console.log('start')runs immediately because it is synchronous.setTimeout()schedules a macrotask. Even with0, it does not run immediately.Promise.resolve().then()schedules a microtask.console.log('end')runs immediately because it is synchronous.- After the synchronous code finishes, the event loop drains the microtask queue, so
promiselogs next. - Then the event loop takes one macrotask, so
timeoutlogs last.
Common mistakes
- Assuming
setTimeout(fn, 0)runs immediately. It only runs after the current synchronous code and all microtasks finish. - Thinking
awaitblocks the thread. It pauses the async function and schedules its continuation as a microtask. - Using recursive microtasks for long work. This can prevent rendering and make the UI unresponsive. Use
setTimeout()orrequestAnimationFrame()when you need to yield. - Forgetting that I/O callbacks are macrotasks. Promise continuations may run before network or timer callbacks.
When to use it
Use the event loop model when you need to understand or control asynchronous ordering. The closest practical comparison is between microtasks and macrotasks.
| Need | Use | Why |
|---|---|---|
| Run immediately after current code | Microtask: Promise.then() or queueMicrotask() |
Microtasks run before the next macrotask. |
| Yield to the browser | Macrotask: setTimeout() |
Allows the event loop to process other tasks and render. |
| Repeat work periodically | Macrotask: setInterval() |
Each interval callback is queued as a new task. |
| Handle user events or I/O | Macrotask | These are normal asynchronous tasks managed by the environment. |
Use microtasks for small continuations. Use macrotasks when you need to give the browser a chance to breathe.
Practice
Guided exercise: predict the output of this code.
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('A'));
console.log('C');
Expected output:
C
A
B
Challenge: write code that logs 1, 2, 3, 4 in that order using one synchronous log, one microtask, and one macrotask.
Solution hint: put 1 in synchronous code, 2 in a Promise.then(), and 3 or 4 in a setTimeout().
Quick check
Question: If a setTimeout() callback and a Promise.then() callback are scheduled in the same synchronous block, which runs first?
Answer: The Promise.then() callback runs first because microtasks are processed before macrotasks.
Summary
JavaScript uses one main thread, and the event loop schedules asynchronous work by moving callbacks from queues onto the call stack. Microtasks run before macrotasks, which is why promise continuations often appear before timer callbacks.