16 JavaScript Event Loop Interview Questions (2026)

·13 min read
By ·Updated
javascriptinterview-questionsevent-loopasyncfrontend

JavaScript code within one agent runs one job at a time, yet browsers and Node.js coordinate many concurrent operations through their host event loops and platform services. Workers can add other agents and threads. When interviewers ask you to predict the output of setTimeout mixed with Promises, they are testing whether you understand tasks, microtask checkpoints, and host-specific scheduling.

Table of Contents

  1. Event Loop Fundamentals Questions
  2. Microtasks vs Macrotasks Questions
  3. Code Output Prediction Questions
  4. Async/Await Questions
  5. Node.js Event Loop Questions
  6. Practical Application Questions
  7. Quick Reference

Event Loop Fundamentals Questions

These questions test your understanding of the event loop's core mechanism.

What is the JavaScript Event Loop?

The browser event loop coordinates tasks, microtasks, rendering, and asynchronous platform work for an agent. It selects a runnable task, performs that task's steps, then runs a microtask checkpoint. A rendering opportunity may follow according to the browser's scheduling and display constraints.

During a JavaScript job, function execution contexts form a stack. Calling setTimeout registers a timer with the host; once the delay threshold has passed, the host queues a timer task. The callback runs only when the event loop selects that runnable task. A timer delay is a minimum threshold, not an exact execution time.

What are the main components of the Event Loop?

The Event Loop consists of several interconnected parts that work together to manage asynchronous execution:

ComponentPurpose
Call StackWhere function execution contexts are managed (LIFO)
Web APIsBrowser-provided APIs that handle async operations (setTimeout, fetch, DOM events)
Task QueuesOne or more queues associated with task sources such as timers, networking, and user interaction
Microtask QueueHolds Promise callbacks, queueMicrotask, MutationObserver
Rendering OpportunitiesBrowser-controlled opportunities to run animation callbacks, update layout, and paint
Event LoopSelects runnable tasks and performs microtask checkpoints

A useful browser simplification is: run one task → drain the microtask queue → possibly render → select another task. The browser may choose among multiple task queues, so "one global macrotask FIFO" is not the standard model.

What happens to the event loop during a long-running script?

The call stack must be empty for the event loop to process any tasks. A long-running synchronous script blocks everything—no events fire, no callbacks execute, the UI freezes.

This is why we never put heavy computation in the main thread without breaking it up. If you have a loop processing 10,000 items synchronously, the browser cannot respond to user clicks, animations freeze, and the page becomes unresponsive.


Microtasks vs Macrotasks Questions

These questions test your understanding of task queue priorities.

What is the difference between microtasks and macrotasks?

After the current task finishes, the event loop performs a microtask checkpoint and keeps dequeuing microtasks until the queue is empty. Microtasks queued by other microtasks run in the same checkpoint, which is why an unbounded chain can delay tasks and rendering.

Microtasks:

  • Promise callbacks (.then(), .catch(), .finally())
  • queueMicrotask()
  • MutationObserver

Regular tasks (often called macrotasks informally):

  • setTimeout / setInterval
  • I/O operations
  • Event handlers (click, scroll, etc.)

Rendering is scheduled through rendering opportunities rather than being just another macrotask. In the usual example where an already-settled Promise reaction and a zero-delay timer are scheduled by the same task, the Promise reaction runs first at the microtask checkpoint.

Why does Promise execute before setTimeout even with 0 delay?

Promise reactions use the microtask queue, while setTimeout queues a timer task after its delay threshold. When both are scheduled during the same task, the microtask checkpoint occurs before the event loop can select the timer task.

console.log('1');
 
setTimeout(() => {
    console.log('2');
}, 0);
 
Promise.resolve().then(() => {
    console.log('3');
});
 
console.log('4');
 
// Output: 1, 4, 3, 2

Step by step:

  1. console.log('1') - Runs immediately, prints 1
  2. setTimeout - Timer registered; its callback becomes a later timer task
  3. Promise.then - Callback sent to microtask queue
  4. console.log('4') - Runs immediately, prints 4
  5. Current task finishes → microtask checkpoint prints 3
  6. A later event-loop turn selects the timer task → prints 2

What is the difference between setTimeout(fn, 0) and queueMicrotask(fn)?

setTimeout(fn, 0) schedules a timer task after a minimum delay, while queueMicrotask(fn) appends directly to the current event loop's microtask queue. If both calls occur in the same task as shown below, the microtask runs first.

Use queueMicrotask for a small follow-up that must run after the current synchronous work but before the event loop selects another task. Do not use it to yield for rendering: recursively queued microtasks can starve rendering and input.

setTimeout(() => console.log('macrotask'), 0);
queueMicrotask(() => console.log('microtask'));
console.log('sync');
 
// Output: sync, microtask, macrotask

Code Output Prediction Questions

These questions test your ability to trace through async code execution.

What is the output of this Promise and setTimeout combination?

This is a classic interview question that separates candidates who memorized answers from those who truly understand:

console.log('start');
 
setTimeout(() => console.log('timeout 1'), 0);
 
Promise.resolve()
    .then(() => {
        console.log('promise 1');
        setTimeout(() => console.log('timeout 2'), 0);
    })
    .then(() => console.log('promise 2'));
 
setTimeout(() => console.log('timeout 3'), 0);
 
console.log('end');

Output:

start
end
promise 1
promise 2
timeout 1
timeout 3
timeout 2

Explanation:

  1. Sync code runs: start, end
  2. Microtasks run: promise 1, then promise 2 (chained .then)
  3. During promise 1, a new setTimeout is queued (timeout 2)
  4. These timer tasks run in registration order: timeout 1, timeout 3, timeout 2

What happens with chained Promise.then callbacks?

In this example, each .then() queues the next Promise reaction while the same microtask checkpoint is being drained, so the whole chain runs before the already-scheduled timer task.

setTimeout(() => console.log('timeout'), 0);
 
Promise.resolve()
    .then(() => console.log('promise 1'))
    .then(() => console.log('promise 2'))
    .then(() => console.log('promise 3'));
 
console.log('sync');

Output:

sync
promise 1
promise 2
promise 3
timeout

Each .then() callback is added to the microtask queue when the previous promise resolves, and the entire microtask queue is drained before processing the setTimeout.

What is the output when a Promise executor runs synchronously?

The Promise constructor's executor function runs synchronously—only the .then() callbacks are asynchronous:

const promise = new Promise((resolve) => {
    console.log('1');
    resolve();
    console.log('2');
});
 
promise.then(() => console.log('3'));
console.log('4');
 
// Output: 1, 2, 4, 3

The executor runs immediately when the Promise is created, so 1 and 2 print synchronously. The resolve() call queues the .then() callback as a microtask, which runs after 4.


Async/Await Questions

These questions test your understanding of how async/await works with the event loop.

How does async/await relate to the event loop?

Async functions and await are specified in terms of Promise capabilities and jobs. An async function runs synchronously until it reaches an await; resuming it after the awaited value settles is scheduled through a Promise job, observed as a microtask in browsers and Node.js.

async function example() {
    console.log('1');
    await Promise.resolve();
    console.log('2'); // This goes to microtask queue
}
 
example();
console.log('3');
 
// Output: 1, 3, 2

The function runs synchronously until await, then pauses. The remaining code (console.log('2')) becomes a microtask.

What is the execution order of async functions?

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
 
async function async2() {
    console.log('async2');
}
 
console.log('script start');
async1();
console.log('script end');

Output:

script start
async1 start
async2
script end
async1 end

Explanation:

  1. script start prints synchronously
  2. async1() is called, prints async1 start
  3. async2() is called, prints async2 synchronously
  4. await pauses async1, the rest becomes a microtask
  5. script end prints synchronously
  6. Call stack empty → microtask runs → async1 end prints

Node.js Event Loop Questions

These questions test your understanding of Node.js-specific event loop behavior.

How is the Node.js event loop different from the browser?

The Node.js event loop has multiple phases that process different types of callbacks. It uses the libuv library for async I/O operations.

Node.js Event Loop Phases:

  1. Timers - executes setTimeout and setInterval callbacks
  2. Pending callbacks - executes I/O callbacks deferred from previous cycle
  3. Idle, prepare - internal use only
  4. Poll - retrieves new I/O events
  5. Check - executes setImmediate callbacks
  6. Close callbacks - executes close event callbacks

Key differences include setImmediate() in the check phase, libuv-backed I/O, and a separate process.nextTick() queue. Starting with libuv 1.45 (Node.js 20), timers run after the poll phase during loop iterations, which can affect the ordering of timers and setImmediate().

What is process.nextTick in Node.js?

process.nextTick() schedules work in Node's separate next-tick queue and is now documented as a legacy API for most userland deferral. Recursively filling that queue can starve I/O. In CommonJS the next-tick queue is drained before the Promise/queueMicrotask queue, but ESM top-level evaluation already runs as a microtask, so the simple ordering can reverse.

// Node.js CommonJS example
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
 
// Output: nextTick, promise

Prefer queueMicrotask() for portable deferral unless you specifically need process.nextTick() semantics.


Practical Application Questions

These questions test your ability to apply event loop knowledge to real problems.

How do you break up heavy computation to avoid blocking?

Long-running synchronous code blocks the UI. Break it into smaller chunks and yield to the event loop between chunks:

// BAD: Blocks the UI
function processLargeArray(array) {
    array.forEach(item => heavyComputation(item));
}
 
// GOOD: Yields to the event loop
function processLargeArrayAsync(array) {
    let index = 0;
 
    function processChunk() {
        const chunkSize = 100;
        const end = Math.min(index + chunkSize, array.length);
 
        while (index < end) {
            heavyComputation(array[index]);
            index++;
        }
 
        if (index < array.length) {
            setTimeout(processChunk, 0); // Yield to event loop
        }
    }
 
    processChunk();
}

Using setTimeout(fn, 0) between chunks allows the browser to handle user events and render updates.

How do you let the browser paint before blocking work?

Returning from the current task does not by itself guarantee that a paint has happened; rendering depends on browser-controlled rendering opportunities. requestAnimationFrame() runs before a paint, so calling alert() inside the first callback can still block that paint. A second animation-frame callback schedules the blocking work for a later frame, after the browser has had an opportunity to present the update:

// BAD: Alert shows before DOM updates
button.textContent = 'Loading...';
alert('Processing!'); // Blocks - user sees old text
 
// Allow a rendering opportunity before blocking in a later frame
button.textContent = 'Loading...';
requestAnimationFrame(() => {
    requestAnimationFrame(() => {
        alert('Processing!');
    });
});

How does debouncing work with the event loop?

Debouncing uses setTimeout and clearTimeout to ensure a function only runs after a period of inactivity:

let timeoutId;
 
searchInput.addEventListener('input', (e) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
        // Only runs after user stops typing for 300ms
        performSearch(e.target.value);
    }, 300);
});

Each keystroke clears the previous timer and sets a new one. The search only runs when no new keystrokes occur for 300ms.


Quick Reference

ConceptWhat to Remember
Call StackLIFO, synchronous execution
Task queuesOne or more queues; timers, I/O, and UI events come from different task sources
Microtask QueuePromises, queueMicrotask, MutationObserver
Browser checkpointTask → drain microtasks → possible rendering opportunity → next task
Promise vs setTimeoutIf scheduled in the same task, an already-settled Promise reaction runs before the timer
BlockingLong sync code freezes everything
async/awaitCode after await goes to microtask queue
process.nextTickNode.js legacy queue; CommonJS and ESM can differ in ordering

Official Sources

Frequently Asked Questions

What is the JavaScript Event Loop?

An event loop coordinates tasks, microtasks, rendering, and asynchronous platform operations for a JavaScript agent. It selects a runnable task, runs its steps, performs a microtask checkpoint, and gives the browser rendering opportunities. JavaScript in one agent executes one job at a time, while browsers can also use workers and parallel platform services.

What is the difference between microtasks and macrotasks?

A browser event loop has one microtask queue and one or more task queues. After a task finishes, the browser drains microtasks such as Promise reactions and queueMicrotask callbacks before selecting another task. 'Macrotask' is informal shorthand for a regular task; rendering is a separate opportunity, not simply another macrotask.

Why does Promise execute before setTimeout even with 0 delay?

When both are scheduled by the same task, a reaction to an already-resolved Promise is a microtask, while setTimeout schedules a later timer task after at least its delay threshold. The microtask checkpoint runs when the current task completes, so that Promise reaction runs before the timer in the standard interview example. This is a contextual rule, not a claim that every Promise callback always beats every timer.

What happens if the call stack is blocked?

If the call stack is blocked by long-running synchronous code, the event loop cannot process any other tasks. This freezes the UI, prevents user interactions, and stops any callbacks from executing. This is why heavy computations should be broken into smaller chunks, moved to Web Workers, or made asynchronous to keep the application responsive.

What are the main components of the Event Loop?

For browser interviews, discuss the JavaScript execution stack, platform APIs, one or more task queues grouped by task source, the microtask queue, and rendering opportunities. The event loop selects a runnable task, executes it, then performs a microtask checkpoint. The HTML Standard does not define one universal FIFO 'macrotask queue'.

How is the Node.js event loop different from the browser?

Node.js uses libuv phases including timers, pending callbacks, poll, check, and close callbacks; setImmediate runs in check. Since libuv 1.45 in Node 20, timers run after poll during loop iterations. process.nextTick has a separate legacy queue and can starve I/O, but its ordering relative to Promise microtasks differs between CommonJS and ESM. Prefer queueMicrotask for portable deferral.

Ready to ace your interview?

Get 550+ interview questions with detailed answers in our comprehensive PDF guides.

View PDF Guides