# How the JavaScript Event Loop and Task Queue Work: A Complete Guide

> Understand the JavaScript event loop and task queue. Learn how it enables single threaded concurrency by monitoring the call stack and processing tasks efficiently.

- Repository: [Lydia Hallie/javascript-questions](https://github.com/lydiahallie/javascript-questions)
- Tags: deep-dive
- Published: 2026-03-04

---

**The JavaScript event loop enables single-threaded concurrency by continuously monitoring the call stack, executing all pending microtasks immediately after the stack clears, and then processing one macrotask at a time.**

The JavaScript event loop is the orchestration mechanism that makes asynchronous programming possible in a single-threaded runtime. According to the comprehensive explanations in the `lydiahallie/javascript-questions` repository, specifically within the [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md) file around lines 941-959, the event loop coordinates the **call stack**, **microtask queue**, and **task queue** to determine exactly when your code executes.

## The Core Architecture of the JavaScript Event Loop

### The Call Stack and Single-Threaded Execution

JavaScript operates on a single thread, meaning only one piece of code runs at any given moment. When a function is invoked, its execution context is pushed onto the **call stack**. The engine processes the code at the top of this stack until completion, then pops it off. As documented in the repository's explanation at line 957 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md), the event loop constantly looks at the stack to determine if it's safe to process queued tasks.

### Web APIs and the Host Environment

Asynchronous operations like `setTimeout`, `fetch`, or DOM events are not part of the JavaScript engine itself—they belong to the **host environment** (browser or Node.js). When you invoke these APIs, the request is handed off to the host, and JavaScript immediately continues executing the current stack without waiting. The host environment manages these operations independently and prepares callbacks for insertion into the appropriate queues once complete.

## Task Queues: Macrotasks vs. Microtasks

### The Macrotask Queue (Task Queue)

Once an asynchronous operation completes, the host environment places its callback into the **task queue** (also called the **macrotask queue**). Typical macrotasks include `setTimeout`, `setInterval`, I/O callbacks, and UI rendering events. As detailed in the repository's visual walkthrough around lines 941-959 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md), the event loop pulls only one macrotask at a time for execution after clearing the microtask queue.

### The Microtask Queue

Promises, `MutationObserver`, and explicitly queued microtasks via `queueMicrotask` utilize a separate **microtask queue**. These tasks have higher priority than macrotasks. According to the repository's explanation at lines 4359-4367 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md), after the call stack becomes empty, the event loop drains **all** pending microtasks before processing the next macrotask. This ensures that promise resolutions and `async/await` continuations execute as soon as possible.

### Priority and Execution Order

The execution priority follows a strict hierarchy: **Call Stack** > **Microtask Queue** > **Macrotask Queue**. When the stack empties, the event loop first executes every microtask in FIFO order, then pulls a single macrotask. This cycle repeats indefinitely. The repository's detailed article reference at line 557 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md) (linking to https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif) provides visual diagrams illustrating this exact flow.

## Code Examples: Event Loop Behavior in Practice

### Macrotasks vs. Microtasks

This example demonstrates the priority difference between `setTimeout` (macrotask) and Promises (microtask):

```javascript
console.log('A');               // 1️⃣ Push onto stack, executes immediately

setTimeout(() => console.log('B'), 0); // 2️⃣ Handed to Web API, queued as macrotask

Promise.resolve().then(() => console.log('C')); // 3️⃣ Queued as microtask

console.log('D');               // 4️⃣ Runs before any queued callbacks

```

**Output:**

```

A
D
C   // Microtask runs after stack empties
B   // Macrotask runs after microtasks drain

```

As explained in the repository's walkthrough at lines 941-959 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md), the event loop processes `C` before `B` because microtasks have priority over macrotasks once the call stack clears.

### Async/Await and Microtasks

The `async/await` syntax implicitly uses Promises, placing continuations in the microtask queue:

```javascript
async function foo() {
  console.log('1');
  await Promise.resolve();   // Yields control, queues remainder as microtask
  console.log('2');
}
foo();
console.log('3');

```

**Output:**

```

1
3
2

```

The `await` keyword pauses execution of `foo` and schedules the remaining code (`console.log('2')`) as a microtask. This executes after the current call stack completes (`console.log('3')`) but before any macrotasks, as detailed in the repository's microtask explanation at lines 4359-4367 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md).

### Chained Microtasks

Multiple chained Promise handlers create sequential microtasks:

```javascript
Promise.resolve()
  .then(() => console.log('first'))
  .then(() => console.log('second'));

console.log('sync');

```

**Output:**

```

sync
first
second

```

Each `.then()` appends a new microtask to the queue. The event loop drains the entire microtask queue before moving to macrotasks, ensuring `first` and `second` execute in order immediately after the synchronous code, as referenced in the repository at lines 4359-4367 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md).

## Summary

- The **JavaScript event loop** enables asynchronous programming in a single-threaded environment by coordinating the call stack, microtask queue, and macrotask queue.
- **Microtasks** (Promises, `queueMicrotask`, `async/await`) have higher priority than **macrotasks** (`setTimeout`, I/O, UI events) and execute immediately after the call stack empties.
- The **call stack** processes synchronous code first, then the event loop drains all microtasks, then processes one macrotask, repeating this cycle indefinitely.
- According to the `lydiahallie/javascript-questions` repository, specifically around lines 941-959 and 4359-4367 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md), understanding this execution order is essential for predicting JavaScript runtime behavior.

## Frequently Asked Questions

### What is the difference between the microtask queue and the task queue in JavaScript?

The **microtask queue** handles higher-priority asynchronous operations like Promise resolutions and `async/await` continuations, while the **task queue** (or macrotask queue) manages lower-priority callbacks such as `setTimeout`, `setInterval`, and I/O operations. According to the `lydiahallie/javascript-questions` repository at lines 4359-4367 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md), the event loop always drains the entire microtask queue before processing a single macrotask.

### Why does setTimeout with 0ms delay execute after Promise.then?

Even with a `0ms` delay, `setTimeout` schedules its callback as a **macrotask**, while `Promise.then` places its handler in the **microtask queue**. As documented in the repository's visual walkthrough at lines 941-959 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md), the event loop prioritizes microtasks over macrotasks, executing all pending microtasks immediately after the call stack empties but before moving to the next macrotask.

### How does async/await interact with the JavaScript event loop?

The `async/await` syntax is built on Promises, meaning every `await` expression effectively pauses function execution and schedules the remaining code as a **microtask**. When the awaited Promise resolves, the continuation is placed in the microtask queue and executes after the current call stack clears but before any macrotasks, as illustrated in the repository's examples at lines 4359-4367 of [`README.md`](https://github.com/lydiahallie/javascript-questions/blob/main/README.md).

### Can microtasks starve the macrotask queue?

Yes, if microtasks are created recursively—for example, by chaining Promises or using `queueMicrotask` in a loop—they can prevent the event loop from reaching the macrotask queue indefinitely. According to the `lydiahallie/javascript-questions` repository, the event loop must drain the entire microtask queue before processing a single macrotask, meaning recursive microtask generation can effectively block timers and I/O callbacks from executing.