The Event Loop: How It All Works

Freshman Year, page 8 of 10. A two-minute read.

Okay, time for the big reveal. How does single-threaded JavaScript juggle timers, clicks and network requests all at once? And why did setTimeout(..., 0) run after the other code?

The answer is a mechanism called the event loop. Let's look at the pieces:

The event loop: when the call stack is empty, the next waiting callback gets its turn
  1. The call stack is where JavaScript runs your code, one function at a time. When you call a function, it goes on top of the stack. When it returns, it comes off. JavaScript can only ever work on whatever is at the top of the stack.
  2. The browser (or Node.js) APIs handle the slow stuff outside of JavaScript: timers ticking, network requests travelling, watching for clicks. JavaScript hands these jobs off and keeps going.
  3. The queue is a waiting line. When a slow job finishes (the timer goes off, the data arrives, the button is clicked), its callback doesn't run immediately. It joins the back of the queue.
  4. The event loop is a little loop that runs forever, asking one question: "Is the call stack empty?" If it is, it takes the next callback from the queue and puts it on the stack to run.

That's why setTimeout(..., 0) runs last. Even though the timer finishes instantly, its callback has to wait in the queue until the call stack is completely empty, which means after all the current code has finished.

Promises get VIP treatment

There's one more detail. Promise callbacks (.then, and the code after an await) go into a special VIP queue called the microtask queue. Whenever the call stack empties, the event loop runs every microtask before it even looks at the regular queue. Can you predict the output of this?

JavaScript
console.log("1");

setTimeout(() => console.log("2"), 0);

Promise.resolve().then(() => console.log("3"));

console.log("4");

It prints

1
4
3
2

Take a guess before you look...

  1. "1" prints immediately (normal code).
  2. The timer's callback is sent to the regular queue.
  3. The promise's callback is sent to the VIP microtask queue.
  4. "4" prints immediately (normal code).
  5. The stack is empty. The event loop checks the VIP queue first: "3".
  6. Then the regular queue: "2".
Professor’s CornerWhy does this matter?

Honestly, for most everyday code, you don't need to think about the event loop. But understanding it explains a lot of "weird" behavior, like why a long, slow loop freezes the whole page (it's hogging the call stack, so no clicks or timers can get a turn), and why code after an await runs later. It's also a classic job interview question!

Reading is step one

CurlyScript turns these notes into a game: short questions, an explanation after every answer, and a test at the end of each grade. Free, with nothing to sign up for.

Start learning