Event Loop Guide: How JavaScript Async Code Actually Gets Scheduled
Dev
Last updated on

Event Loop Guide: How JavaScript Async Code Actually Gets Scheduled


Once people spend a little time with asynchronous JavaScript, they eventually run into the event loop. Even after using Promise, setTimeout, and async/await, it is still very common to wonder why some code runs immediately while other code runs later.

That is where the event loop starts to make JavaScript feel much less mysterious. It helps answer the classic question: if JavaScript seems to execute one thing at a time, how does asynchronous behavior still work?

This guide covers:

  • what the event loop is
  • what the call stack, task queue, and microtasks do
  • why Promise callbacks often appear before timer callbacks

The key idea is this: the event loop is not the thing performing every async operation itself. It is the coordination mechanism that moves ready work onto the call stack at the right time.

Why does the event loop matter?

JavaScript often looks single-threaded from the way code is written. But real applications constantly deal with:

  • click events
  • network responses
  • setTimeout callbacks
  • Promise continuations
  • async/await follow-up logic

If you do not understand how those pieces are scheduled, debugging async behavior quickly becomes frustrating. Questions like “why did this log appear first?” or “why did the UI pause for a moment?” usually lead back to the event loop.

So this is not just theory. It is part of reading execution order and responsiveness correctly.

Three pieces to learn first

At a beginner level, three ideas are enough to build a solid mental model:

  • call stack
  • task queue
  • microtask queue

The event loop keeps checking those pieces and deciding what gets a turn next.

1. Call stack

This is where currently executing functions live. JavaScript normally runs whatever is on top of this stack.

2. Task queue

This is where later work waits. Timer callbacks, DOM events, and other deferred tasks can wait here until they become eligible to run.

3. Microtask queue

This is where Promise continuations, queueMicrotask, and await follow-up work often go. These tasks are usually processed before normal timer-style tasks.

A useful practical summary is:

  • current work lives on the call stack
  • later work waits in queues
  • Promise-based follow-up work often gets processed before timer callbacks

What does the event loop actually do?

The event loop keeps checking questions like:

  1. is the call stack clear enough for more work?
  2. are there microtasks ready to run?
  3. if not, are there regular queued tasks ready?

So the event loop is closer to a scheduler of turns than a magical async worker.

That is why understanding the event loop makes execution order feel more predictable.

The most basic example

console.log('A');

setTimeout(() => {
  console.log('B');
}, 0);

console.log('C');

Many beginners expect A, B, C, but the usual result is:

A
C
B

Why? Because the timeout callback does not jump directly onto the call stack. It becomes eligible later, and only runs after the current stack work finishes.

So setTimeout(fn, 0) is not “run immediately.” It is closer to “run after current stack work completes, as soon as the scheduler allows.”

Why do Promises often look faster than timers?

This is one of the most common beginner confusion points.

console.log('start');

setTimeout(() => console.log('timeout'), 0);

Promise.resolve().then(() => console.log('promise'));

console.log('end');

A common result is:

start
end
promise
timeout

That happens because Promise continuations are usually handled as microtasks, and microtasks are normally drained before the event loop moves on to regular queued tasks like timer callbacks.

At a beginner level, it is enough to remember:

  • current stack finishes
  • microtasks get handled
  • then regular task queue callbacks get a turn

That is why Promise-based follow-up logic often appears before setTimeout.

How does async/await relate to the event loop?

async/await makes code read in a more top-to-bottom way, but the behavior still sits on top of Promise scheduling.

async function run() {
  console.log('1');
  await Promise.resolve();
  console.log('2');
}

console.log('start');
run();
console.log('end');

This usually produces:

start
1
end
2

That means the code after await does not keep executing on the same call stack turn immediately. It resumes later through Promise-based continuation scheduling.

So async/await is easier syntax, not a separate execution model.

Why does the browser sometimes feel frozen?

The event loop is not only about async order. It also helps explain responsiveness problems.

If a CPU-heavy task holds the call stack for too long:

  • click handling is delayed
  • rendering is delayed
  • Promise follow-up work is delayed
  • timer callbacks are delayed

So having an event loop does not mean the system stays smooth automatically. If current stack work runs too long, everything else waits behind it.

Common misunderstandings

1. setTimeout(fn, 0) means instant execution

No. It means the callback becomes eligible after current stack work completes, not that it jumps ahead instantly.

2. The event loop performs all async operations itself

It is more accurate to think of it as the coordinator of execution timing rather than the thing performing every external operation.

3. If I use async/await, I no longer need the event loop concept

The syntax gets easier, but execution-order questions still lead back to the event loop.

A good beginner exercise

  1. run examples with console.log and setTimeout
  2. add Promise examples and compare the order
  3. rewrite the same flow with async/await
  4. compare your expected order with the actual order

That observation-based approach makes the event loop much easier to understand than theory alone.

FAQ

Q. Is the event loop only a browser concept?

No. It also matters in runtimes like Node.js.

Q. Why do Promise callbacks and setTimeout run in different orders?

Because microtasks and regular queued tasks are not usually processed with the same priority.

Q. Do I need a perfect event loop model before writing async JavaScript?

Not at first. But when execution order feels surprising, the event loop is exactly the concept that helps.

Start Here

Continue with the core guides that pull steady search traffic.

Sponsored