🔄

Event Loop

advanced

The Event Loop is how JavaScript handles asynchronous operations despite being single-threaded. It continuously checks if the call stack is empty, then moves callbacks from the task queues to the stack for execution.

🎮Interactive Visualization

CodeSync
1console.log('1');
2
3setTimeout(() => {
4 console.log('timeout');
5}, 0);
6
7Promise.resolve()
8 .then(() => console.log('promise'));
9
10console.log('2');
Call Stack
<script>
Microtasks
(empty)
Macrotasks
(empty)
Output
Step 1/10Script starts executing. Global EC pushed to call stack.
Key Insight: Microtasks (Promises) always run before macrotasks (setTimeout), even with 0ms delay!

Key Points

  • JavaScript is single-threaded (one call stack)
  • Web APIs handle async operations (setTimeout, fetch, etc.)
  • Task Queue (Macrotasks): setTimeout, setInterval, I/O
  • Microtask Queue: Promises, queueMicrotask, MutationObserver
  • Microtasks run before the next macrotask
  • Event loop: Stack empty? → Run all microtasks → Run one macrotask → Repeat

💻Code Examples

Promise vs setTimeout

console.log("1");

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

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

console.log("4");

// Output: 1, 4, 3, 2
// Microtasks before macrotasks!

Microtasks (Promise) run before macrotasks (setTimeout)

Sync Code Flow

function greet() {
  console.log("Hello");
}

console.log("Start");
greet();
console.log("End");

// Output: Start, Hello, End
// Synchronous = line by line

Synchronous code executes line by line on call stack

Chained Promises

Promise.resolve(1)
  .then(x => {
    console.log(x);  // 1
    return x + 1;
  })
  .then(x => {
    console.log(x);  // 2
    return x + 1;
  })
  .then(x => {
    console.log(x);  // 3
  });

// Each .then queues a microtask
// when the previous resolves

Each .then queues when previous resolves

async/await

async function example() {
  console.log("1");

  await Promise.resolve();
  // Everything after await becomes
  // a microtask

  console.log("2");
}

console.log("A");
example();
console.log("B");

// Output: A, 1, B, 2

await pauses function, queues continuation as microtask

Nested setTimeout

setTimeout(() => {
  console.log("First macrotask");

  setTimeout(() => {
    console.log("Second macrotask");
  }, 0);

}, 0);

console.log("Sync");

// Output: Sync, First, Second
// Each setTimeout queues one
// macrotask for next loop cycle

Nested creates new macrotask for next iteration

Microtask in Macrotask

setTimeout(() => {
  console.log("Macro 1");

  Promise.resolve().then(() => {
    console.log("Micro inside");
  });

  console.log("Macro 1 end");
}, 0);

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

// Output: Macro 1, Macro 1 end,
//         Micro inside, Macro 2

Microtasks created during macrotask run before next macrotask

queueMicrotask

queueMicrotask(() => {
  console.log("Microtask 1");

  queueMicrotask(() => {
    console.log("Microtask 2");
  });
});

console.log("Sync");

// Output: Sync, Microtask 1, Microtask 2
// All microtasks drain before
// any macrotask runs

Direct microtask scheduling, can queue more microtasks

Microtask Starvation

// DANGER: This blocks forever!
function recursive() {
  Promise.resolve().then(recursive);
}

recursive();
setTimeout(() => {
  console.log("Never runs!");
}, 0);

// Microtasks keep adding more
// macrotasks are STARVED

Infinite microtasks block all macrotasks forever!

Common Mistakes

  • Thinking setTimeout(fn, 0) runs immediately
  • Not understanding microtask priority over macrotasks
  • Blocking the event loop with long-running synchronous code

Interview Tips

  • Draw the event loop diagram (stack, queues, Web APIs)
  • Know the order: sync → microtasks → macrotasks
  • Explain why Promises are faster than setTimeout

🔗Related Concepts