What is the event loop in JavaScript?
The short answer
The event loop is a mechanism that allows JavaScript to perform non-blocking operations even though it runs on a single thread. It continuously checks if the call stack is empty, and when it is, it picks up tasks from the queues and pushes them onto the call stack for execution.
JavaScript is single-threaded
Before we understand the event loop, we need to understand what "single-threaded" means.
JavaScript has only one call stack. That means it can do only one thing at a time. If a function is running, nothing else can run until that function finishes. Think of it like a single-lane road — one car at a time.
But here is the problem. What happens when you need to do something that takes time, like fetching data from a server? If JavaScript waited for every network request to finish before doing anything else, the entire page would freeze. You would not be able to click buttons, scroll, or type.
This is where the event loop comes in. It lets JavaScript hand off slow work to the browser, keep doing other things, and come back to handle the result when it is ready.
The pieces that make it work
To understand the event loop, you need to know about four things:
1. Call Stack — This is where JavaScript keeps track of what function is currently running. When you call a function, it gets pushed onto the stack. When the function finishes, it gets popped off.
2. Web APIs — These are provided by the browser (not JavaScript itself). Things like setTimeout, fetch, DOM events, and setInterval are all Web APIs. When you call setTimeout, JavaScript hands it off to the browser and moves on.
3. Callback Queue (Task Queue) — When a Web API finishes its work (like a timer expiring or a click happening), the callback function gets placed in this queue. It waits here until the call stack is empty.
4. Microtask Queue — This is a special queue with higher priority than the callback queue. Promises (.then, .catch, .finally) and MutationObserver callbacks go here. After each task from the call stack finishes, the event loop empties the entire microtask queue before picking anything from the callback queue.
How the event loop works step by step
Let me walk you through a real example:
console.log('Start');setTimeout(function () { console.log('Timeout');}, 0);Promise.resolve().then(function () { console.log('Promise');});console.log('End');Here is what happens:
Step 1: console.log('Start') goes on the call stack, executes, prints Start, and gets popped off.
Step 2: setTimeout goes on the call stack. JavaScript hands the callback to the browser's timer API with a delay of 0ms. setTimeout itself gets popped off the call stack. The browser starts the timer.
Step 3: Promise.resolve().then(...) goes on the call stack. The promise resolves immediately, so the .then callback gets placed in the microtask queue. The promise code gets popped off the call stack.
Step 4: console.log('End') goes on the call stack, executes, prints End, and gets popped off.
Step 5: The call stack is now empty. The event loop checks the microtask queue first. It finds the promise callback, pushes it onto the call stack, it executes and prints Promise.
Step 6: The microtask queue is now empty. The event loop checks the callback queue. The timer callback is there (the browser timer finished). It pushes the callback onto the call stack, it executes and prints Timeout.
Output:
StartEndPromiseTimeoutCommon Pitfalls
A very common mistake candidates make is saying setTimeout with 0ms delay runs immediately. It does not. Even with 0ms, the callback still goes through the callback queue and has to wait for the call stack to be empty AND for all microtasks to finish. This is why Promise prints before Timeout in the example above — microtasks always run before regular callbacks.
Microtask queue vs callback queue
This is the part that trips up most candidates. The event loop does not treat all queues equally.
After each task completes and the call stack is empty, the event loop does the following:
- Run all pending microtasks (promises,
queueMicrotask) - Then pick one task from the callback queue
This means if a microtask adds another microtask, that new microtask also runs before any callback queue task. The microtask queue is fully drained before moving on.
Promise.resolve().then(() => { console.log('Microtask 1'); Promise.resolve().then(() => { console.log('Microtask 2'); });});setTimeout(() => { console.log('Timeout');}, 0);Output:
Microtask 1Microtask 2TimeoutMicrotask 2 runs before Timeout even though Microtask 2 was added after setTimeout was called. This is because the event loop drains the entire microtask queue before touching the callback queue.
Interview Tip
When explaining the event loop in an interview, use a concrete example like the one above. Walk through it step by step — what goes on the call stack, what goes to the Web API, what ends up in which queue, and in what order things execute. Interviewers are testing if you can trace through the execution order, not if you can recite a definition. If you can get the output right and explain why, you have nailed it.
A visual way to think about it
I like to think of the event loop as a restaurant.
The call stack is the chef — they can only cook one dish at a time. The Web APIs are the prep staff — they handle the slow work (peeling, marinating) in the background. The callback queue is the order counter where finished prep work gets placed. The event loop is the expeditor who checks: "Is the chef free? Yes? Here is the next thing to cook."
The chef (call stack) never goes to the prep station to wait. They stay at their station and keep cooking. When prep is done, it goes to the counter (queue), and the expeditor (event loop) brings it to the chef when they are free.
setTimeout is not guaranteed timing
Another important thing to understand — setTimeout(fn, 1000) does not mean "run this function in exactly 1000ms." It means "after at least 1000ms, put this callback in the queue." If the call stack is busy with a heavy computation, the callback will wait longer.
const start = Date.now();setTimeout(() => { console.log(`Ran after ${Date.now() - start}ms`);}, 100);// Simulate heavy workwhile (Date.now() - start < 500) { // blocking the thread for 500ms}Even though the timer is 100ms, the callback will not run until after 500ms because the while loop is blocking the call stack. The event loop cannot pick up the callback until the call stack is empty.
Why interviewers ask this
The event loop is one of the most asked JavaScript interview questions because it tests whether you truly understand how JavaScript works under the hood. It is not just about knowing the definition — interviewers want to see if you can predict the output of asynchronous code, if you understand why promises run before setTimeout, and if you know what happens when the main thread is blocked. A strong understanding of the event loop shows you can debug timing issues, write performant code, and reason about asynchronous behavior — skills that matter in every frontend role.