Compile Ready
Module 8 · Asynchronous JavaScript

The Task (Macrotask) Queue

Intermediate12m read14m practice26m total
Task QueueMacrotaskTimersSchedulingEvent Loop

Introduction

The task queue, commonly called the macrotask queue in interviews, holds callbacks for future event-loop turns: timers, UI events, message events, and other host-driven work.

A task is bigger than a microtask. The event loop runs one task, lets it finish completely, drains all microtasks created by that task, and only then chooses another task.

Why This Matters

Most async output puzzles depend on recognizing when something becomes a future task. Timer callbacks do not interleave with synchronous code, and a timer scheduled inside another timer usually goes behind already queued timer tasks. This is the difference between merely knowing setTimeout and actually tracing the queue.

Theory

What counts as a task

The initial script is a task. After it starts, it runs to completion. Other common task sources include setTimeout, setInterval, DOM event callbacks, postMessage, MessageChannel, and many I/O completion callbacks in host environments.

The term macrotask is popular in tutorials and interviews. The HTML specification talks about task queues and task sources. In interviews, saying task queue, often called macrotask queue is both practical and accurate.

FIFO within a source, not a global guarantee for everything

For simple interview snippets with multiple setTimeout(..., 0) calls, callbacks generally run in the order they are queued. Real browsers have multiple task sources and scheduling policy, so do not overstate that there is one universal FIFO queue for every possible async source.

Task versus microtask

A task represents a new event-loop turn. A microtask is a checkpoint that runs after the current stack clears and before the next task. Therefore, if a timer callback schedules a promise callback, that promise callback runs before the next timer callback.

Nested timers

If task A schedules a new timer while task B is already queued, the new timer does not jump ahead of B. It becomes a future task. This is a common interview trick.

Rendering and responsiveness

Browsers may render between tasks after microtasks drain. Long tasks block input, rendering, and timers because the event loop cannot move on while the call stack is busy. Performance tooling often calls any task longer than about 50ms a long task.

Visual Diagrams

Tasks are event-loop turns

Task queue
  |
  v
+------------------+      microtask checkpoint      +------------------+
| Run one task     | -----------------------------> | Drain microtasks |
| until stack empty|                                | until empty      |
+------------------+                                +------------------+
  |
  v
Browser may render, then pick another task

Only one task is executed at a time, but all microtasks drain after it.

Nested timer ordering

Initial script queues:
  task A: timer A
  task B: timer B

Run task A:
  logs A
  queues task C: nested timer C

Queue is now:
  task B
  task C

A nested timer is appended after already queued timer work.

Code Examples

Timer task schedules a microtask

The microtask created inside the first timer drains before the second timer task.

Loading…

Long tasks block the next task

A busy synchronous task prevents timers and event handlers from running. Avoid this shape in production UI code.

Loading…

Playground

Loading editor…
Console

Press Run to execute the code and see output here.

Output Prediction

Predict the output #1

javascript
1
2console.log('one');
3
4setTimeout(function () {
5 console.log('two');
6}, 0);
7
8setTimeout(function () {
9 console.log('three');
10}, 0);
11
12Promise.resolve().then(function () {
13 console.log('four');
14});
15
16console.log('five');

Predict the output #2

javascript
1
2setTimeout(function () {
3 console.log('timer A');
4
5 setTimeout(function () {
6 console.log('timer C');
7 }, 0);
8}, 0);
9
10setTimeout(function () {
11 console.log('timer B');
12}, 0);
13
14console.log('sync');

Coding Exercises

Yield work in chunks

Medium

Implement processInTasks(items, size, work, done) so it processes size items synchronously, then yields with setTimeout(..., 0) before processing the next chunk. Call done() after all items are processed.

Interview Questions

1What is a macrotask, and how is it different from a microtask?

A macrotask, more precisely a task, is a full event-loop turn such as the initial script, a timer callback, or an event callback. A microtask is a smaller callback checkpoint, such as a promise reaction, that runs after the current task's stack is empty and before the next task starts.

Asked at:NetflixMetaGoogle
2If a timer callback schedules another timer, can the nested timer run before an already queued timer?

In the normal timer examples interviewers use, no. The nested timer is scheduled only when the first timer task runs, so it is queued behind timer tasks that were already eligible and waiting.

Follow-ups

  • What if the first timer schedules a promise instead?
  • Why can long tasks hurt UI responsiveness?

Quiz

1. Which item is typically a task/macrotask?

2. What happens after a timer task finishes before the next timer task runs?

Summary

  • Tasks are full event-loop turns; timer and event callbacks are common task sources.
  • The initial script itself runs as a task.
  • After one task completes, all microtasks drain before the next task.
  • Nested timers are future tasks and do not jump ahead of already queued work.

Cheat Sheet

Task/macrotask examples: initial script, setTimeout, setInterval, DOM events, message events.

After every task: call stack empty → drain all microtasks → maybe render → next task.

Nested timer trick: a timer scheduled inside timer A goes behind timer B if B was already queued.

Performance: long tasks block input, rendering, and async callbacks.