Event Loop & Concurrency

10 questions found

Is JavaScript single-threaded, and what does that mean in practice?

Beginner
Yes, JavaScript runs on a single main thread, executing one operation at a time. It achieves the appearance of doing multiple things at once through asynchronous callbacks, Promises, and the event loop — not through true parallel execution on that thread.
console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
// logs: 1, 3, 2
Real-world example Understanding why a long synchronous loop freezes the entire page, including scrolling and clicks.

Common follow-ups: How do Web Workers get around the single-threaded limitation?

Web Workers & Multithreading

What is the call stack?

Beginner
The call stack tracks which function is currently executing and what called it, growing with each function call and shrinking as functions return — a 'stack overflow' happens when it grows too deep, usually from unbounded recursion.
function a() { b(); }
function b() { c(); }
function c() { console.log('deepest'); }
a(); // stack: a -> b -> c, then unwinds
Real-world example Reading a stack trace in an error to see the exact chain of function calls that led to a crash.

Common follow-ups: What causes a 'Maximum call stack size exceeded' error?

Error Handling

What is the difference between the microtask queue and the macrotask (task) queue?

Intermediate
Promise callbacks (.then, async/await continuations) go into the microtask queue, which is fully drained after every single macrotask completes. setTimeout, setInterval, and I/O callbacks go into the macrotask queue, processed one at a time between event loop ticks.
console.log('1');
setTimeout(() => console.log('2 (macrotask)'), 0);
Promise.resolve().then(() => console.log('3 (microtask)'));
console.log('4');
// logs: 1, 4, 3, 2
Real-world example Understanding why a Promise callback always runs before a setTimeout(fn, 0), even though both are 'async'.

Common follow-ups: What happens if a microtask queues another microtask — does it get processed in the same cycle?

Promises & async/await

How does the event loop decide what to run next?

Intermediate
After each synchronous script (or a single macrotask) finishes, the event loop first fully drains the microtask queue, then renders any pending UI updates, then picks up the next macrotask from the queue — repeating this cycle continuously.
// Simplified loop:
// while (true) {
//   runOneMacrotask();
//   drainAllMicrotasks();
//   renderIfNeeded();
// }
Real-world example Explaining why UI updates can appear to 'batch' after several synchronous state changes but before the next repaint.

Common follow-ups: Where do requestAnimationFrame callbacks fit into this cycle?

Async Iterators & Streams

Why does a long synchronous function block UI updates and event handling?

Intermediate
Since JS runs on one thread shared with rendering and input handling, any synchronous code that takes a long time to finish prevents the browser from processing clicks, repainting the screen, or running queued callbacks until that function returns.
function blockFor(ms) {
  const end = Date.now() + ms;
  while (Date.now() < end) {} // busy-waits, freezing the page
}
blockFor(3000); // UI is frozen for 3 seconds
Real-world example Diagnosing a frozen 'Not Responding' UI caused by a synchronous JSON.parse() on a huge payload.

Common follow-ups: How would you break up a long synchronous task to avoid blocking the UI?

Performance Optimization: Debouncing Throttling & Memoization

Why does Promise.resolve().then(cb) run before setTimeout(cb, 0), even with a 0ms delay?

Advanced
setTimeout(fn, 0) doesn't run immediately — it's still scheduled as a macrotask, which the browser also clamps to a minimum delay (often ~4ms after nesting). Promise callbacks are microtasks, and ALL pending microtasks are drained before the event loop even considers the next macrotask.
setTimeout(() => console.log('macro'), 0);
Promise.resolve().then(() => console.log('micro'));
// logs: 'micro' then 'macro', every time
Real-world example A subtle bug where code assumed setTimeout(fn, 0) would run 'immediately after' the current synchronous code, before any promises.

Common follow-ups: Is there any way to schedule work that runs before all microtasks?

Promises & async/await

How would you break a large synchronous task into chunks to keep the UI responsive?

Advanced
Split the work into smaller batches and yield control back to the event loop between them using setTimeout(fn, 0) or requestIdleCallback(), allowing pending UI updates and user input to be processed between chunks.
function processInChunks(items, chunkSize, fn) {
  let i = 0;
  function next() {
    const chunk = items.slice(i, i + chunkSize);
    chunk.forEach(fn);
    i += chunkSize;
    if (i < items.length) setTimeout(next, 0);
  }
  next();
}
Real-world example Processing 100,000 rows of imported CSV data client-side without freezing the browser tab.

Common follow-ups: How does requestIdleCallback differ from setTimeout for this purpose?

Performance Optimization: Debouncing Throttling & Memoization

What is 'starvation' of the macrotask queue, and how can recursive microtasks cause it?

Advanced
Because ALL pending microtasks are drained before the next macrotask runs, code that keeps scheduling new microtasks from within a microtask (e.g. a Promise chain that queues another .then() indefinitely) can starve macrotasks — including rendering and setTimeout callbacks — from ever running.
function foreverMicrotask() {
  Promise.resolve().then(foreverMicrotask); // never lets a macrotask run
}
foreverMicrotask(); // freezes rendering indefinitely
Real-world example Debugging a page that becomes completely unresponsive due to a recursive .then() chain with no exit condition.

Common follow-ups: How would you rewrite this to periodically yield to macrotasks?

Promises & async/await

How does async/await map onto the microtask queue under the hood?

Advanced
An async function returns immediately at its first await, suspending execution; the code after await is scheduled to resume as a microtask once the awaited Promise settles — functionally equivalent to chaining .then() callbacks, just with more readable syntax.
async function run() {
  console.log('1');
  await null; // yields, resumes as a microtask
  console.log('2');
}
run();
console.log('3');
// logs: 1, 3, 2
Real-world example Understanding why code immediately after an awaited call doesn't run synchronously, even for an already-resolved value.

Common follow-ups: Does 'await null' behave differently from 'await Promise.resolve()'?

Promises & async/await

What's the relationship between requestAnimationFrame and the event loop's rendering step?

Advanced
requestAnimationFrame schedules a callback to run right before the browser's next repaint, after microtasks have drained — making it the right tool for visual updates synced to the display's refresh rate, unlike setTimeout which has no such guarantee.
function animate() {
  element.style.left = `${position++}px`;
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Real-world example Building a smooth custom animation that stays in sync with the browser's actual repaint cycle, avoiding jank.

Common follow-ups: Why is requestAnimationFrame generally preferred over setInterval for animations?

Performance Optimization: Debouncing Throttling & Memoization