Loading...
Loading...
By continuing to use the platform, you accept the terms of the Privacy Policy and the use of cookies.
Event Loop is a mechanism in JavaScript that manages asynchronous tasks and event queues. It allows JavaScript to work in a single-threaded model, processing asynchronous operations without blocking the main execution thread.
Event Loop is an infinite loop that executes event handlers. During its operation, the browser distributes tasks into two main queues:
.then, .catch, .finally), queueMicrotask(), MutationObserver.setTimeout, setInterval, DOM events (click, input), MessageChannel, requestAnimationFrame.fetch is not a macrotask:
A common interview mistake: treating fetch as a macrotask. In reality, fetch() returns a Promise. The network request runs in Web API, but when the response arrives, the .then() callback goes into the microtask queue, not the macrotask queue. Remember: everything that works through Promises (fetch, async/await) is processed as a microtask.
All synchronous tasks from the Call Stack execute first.
After synchronous code completes, all microtasks execute. Every microtask runs before the browser picks up the next macrotask. If a microtask adds a new microtask, it also runs in the same cycle.
The browser checks if a repaint is needed. If there are DOM or style changes, it runs: requestAnimationFrame callbacks, style recalculation, layout, paint.
One macrotask is taken from the queue and executed. Then the cycle repeats from step 2.
Call Stack (LIFO — Last In, First Out) — contains all currently executing functions. When a function is called, it is added to the stack. After execution, it is removed. If another function is called inside, it is pushed on top of the stack.
Web API — asynchronous operations (setTimeout, event handlers, network requests) are passed to Web API. This is an environment provided by the browser (or Node.js). After the async operation completes, its callback is placed in the appropriate queue.
Task Queue / Callback Queue (FIFO — First In, First Out) — stores macrotask callbacks. Event Loop moves tasks from this queue to the Call Stack when the stack is empty.
Microtask Queue — a separate queue for microtasks. Has priority over the Task Queue. Fully drained after every synchronous task or macrotask.
Macrotasks:
setTimeout, setIntervalclick, input, load)MessageChannel, postMessagesetImmediate (Node.js)Microtasks:
.then(), .catch(), .finally() (Promise)async/await (continuation after await)queueMicrotask()MutationObserverprocess.nextTick() (Node.js, higher priority than other microtasks)console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
Result: 1, 4, 3, 2
Breakdown:
console.log("1") — synchronous code, runs immediatelysetTimeout — callback goes to macrotask queuePromise.resolve().then(...) — callback goes to microtask queueconsole.log("4") — synchronous code, runs immediately3 is printed2 is printedImportant:
Microtasks have priority over macrotasks. Even if setTimeout is called with delay 0, its callback will only run after all microtasks.
console.log("1");
setTimeout(() => {
console.log("2");
Promise.resolve().then(() => console.log("3"));
}, 0);
Promise.resolve().then(() => {
console.log("4");
setTimeout(() => console.log("5"), 0);
});
setTimeout(() => console.log("6"), 0);
Promise.resolve().then(() => console.log("7"));
console.log("8");
Result: 1, 8, 4, 7, 2, 3, 6, 5
Step-by-step breakdown:
Synchronous code: 1 and 8 are printed.
Microtasks (first cycle): 4 is printed (first Promise), then 7 (second Promise). Inside the callback for 4, a setTimeout with console.log("5") is created and goes to the macrotask queue.
First macrotask: setTimeout with console.log("2"). Prints 2. Inside it, a Promise with console.log("3") is created — this is a microtask, runs immediately: prints 3.
Second macrotask: setTimeout with console.log("6"). Prints 6.
Third macrotask: setTimeout with console.log("5") (created inside the microtask). Prints 5.
requestAnimationFrame (rAF) occupies a special place in the Event Loop. It runs after microtasks but before macrotasks, right before rendering.
setTimeout(() => console.log("setTimeout"), 0);
requestAnimationFrame(() => console.log("rAF"));
Promise.resolve().then(() => console.log("Promise"));
console.log("sync");
Expected result: sync, Promise, rAF, setTimeout
In practice, the order of rAF and setTimeout may vary across browsers, but rAF always runs before the next render cycle. Use requestAnimationFrame for animations and DOM manipulation.
Interviewers often ask how async/await works in the context of the Event Loop. Code after await becomes a callback inside .then(), meaning it goes into the microtask queue.
async function foo() {
console.log("1");
await Promise.resolve();
console.log("2");
}
console.log("3");
foo();
console.log("4");
Result: 3, 1, 4, 2
Breakdown:
console.log("3") — synchronous codefoo() is called: console.log("1") runs synchronouslyawait Promise.resolve() pauses foo. Everything after await becomes a microtaskconsole.log("4") — synchronous code continuesconsole.log("2")The key point: await does not block the main thread. It "splits" the function into two parts: before await (synchronous) and after await (microtask).
queueMicrotask() allows you to explicitly add a task to the microtask queue. Useful when you need to defer code execution but run it before any macrotasks.
console.log("1");
queueMicrotask(() => console.log("2"));
setTimeout(() => console.log("3"), 0);
queueMicrotask(() => console.log("4"));
console.log("5");
Result: 1, 5, 2, 4, 3
queueMicrotask has the same priority as .then(). Both microtasks (2 and 4) run before the setTimeout macrotask (3).
If microtasks keep adding new microtasks indefinitely, macrotasks and rendering will never execute. This is called starvation.
function recursiveMicrotask() {
Promise.resolve().then(() => {
console.log("microtask");
recursiveMicrotask();
});
}
recursiveMicrotask();
setTimeout(() => console.log("this will never run"), 0);
The browser will freeze because the microtask queue never empties. Rendering is blocked, setTimeout will never execute. In an interview you might be asked: "Can a Promise block rendering?" The answer is yes, if microtasks keep adding new microtasks indefinitely.