Event Loop & Call Stack in JavaScript - Exercise 3

It occurs when code continually adds microtasks, preventing the event loop from reaching regular tasks such as timers.

function keepBusy() {
  queueMicrotask(keepBusy);
}
// keepBusy(); // starves the task queue

A long-running function occupies the call stack, so timers, input, rendering, and network callbacks must wait.

function blockFor(milliseconds) {
  const start = Date.now();
  while (Date.now() - start < milliseconds) {}
}
blockFor(200);

Process a small batch and schedule the next batch as a task so other work can run between batches.

function processBatch(items, index = 0) {
  const nextIndex = Math.min(index + 100, items.length);
  for (let i = index; i < nextIndex; i += 1) process(items[i]);
  if (nextIndex < items.length) setTimeout(() => processBatch(items, nextIndex), 0);
}

Promise handlers are microtasks, which are drained before the event loop takes the next task from the callback queue.

button.addEventListener('click', () => console.log('click task'));
Promise.resolve().then(() => console.log('promise microtask'));
// The Promise handler runs before a later click task.

Tasks include timers and DOM events; microtasks include Promise reactions and queueMicrotask callbacks. Microtasks run between tasks.

setTimeout(() => console.log('task'), 0);
queueMicrotask(() => console.log('microtask'));
// microtask runs before task

It schedules visual updates before the browser's next repaint, making it suitable for animation rather than arbitrary background work.

let position = 0;
function animate() {
  position += 1;
  box.style.transform = `translateX(${position}px)`;
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

Use bounded batches, yield with a task scheduler, and avoid unbounded synchronous loops or microtask chains.

async function processAll(items) {
  for (const item of items) {
    process(item);
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

Label synchronous calls, task scheduling, microtask scheduling, and each callback, then trace when the stack empties and which queue has priority.

console.log('A');
setTimeout(() => console.log('D'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('B');
// A, B, C, D