Event Loop & Call Stack in JavaScript - Exercise 2

The task queue holds callbacks from operations such as timers and DOM events until the event loop can run them. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.

javascript

setTimeout(() => console.log('task one'), 0);
button.addEventListener('click', () => console.log('event task'));

A microtask is high-priority asynchronous work, commonly created by Promise handlers or queueMicrotask. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.

javascript

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

The Promise handler runs first because the event loop drains microtasks before taking the next task. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.

javascript

setTimeout(() => console.log('timer'), 0);
Promise.resolve().then(() => console.log('promise'));
// promise, timer

JavaScript runs every queued microtask before moving to the next task, including microtasks added by earlier microtasks. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.

javascript

queueMicrotask(() => {
  console.log('first');
  queueMicrotask(() => console.log('second'));
});
setTimeout(() => console.log('timer'), 0);

The code after await pauses and resumes as a Promise microtask, while synchronous code outside the async function can continue. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.

javascript

async function load() {
  console.log('before await');
  await Promise.resolve();
  console.log('after await');
}
load();
console.log('outside');
// before await, outside, after await

The delay is a minimum wait. The callback still waits for the current stack and earlier queued work to finish. The example makes the scheduling boundary visible without relying on implementation-specific timing.

javascript

const start = Date.now();
while (Date.now() - start < 100) {}
setTimeout(() => console.log('runs after the blocking loop'), 0);

Each fulfilled handler schedules the next handler as another microtask. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.

javascript

Promise.resolve()
  .then(() => console.log('step one'))
  .then(() => console.log('step two'));
setTimeout(() => console.log('task'), 0);

Synchronous code runs first, then microtasks, then timer or other task callbacks. This ordering explains why asynchronous work cannot interrupt the synchronous code that is already running.

javascript

console.log('sync');
Promise.resolve().then(() => console.log('microtask'));
setTimeout(() => console.log('task'), 0);
// sync, microtask, task

Promise reactions are microtasks, and runtimes drain the microtask queue after the current stack before taking the next task such as a timer.

javascript

Promise.resolve().then(() => console.log('promise'));
setTimeout(() => console.log('timer'), 0);
// promise, timer

Each microtask can queue another microtask, so a long chain keeps the runtime draining that queue before it returns to timer or rendering work.

javascript

let count = 0;
function queueNext() {
  if (count++ < 2) queueMicrotask(queueNext);
}
queueNext();
All sections expanded. Activate the control to collapse all sections.