🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
JS MASTER CLASS /// MASTER THE ENGINE /// BUILD LOGIC /// ASYNC PATTERNS /// JS MASTER CLASS /// MASTER THE ENGINE ///

Macrotasks | JavaScript Tutorial - In-Depth Guide

Focus specifically on macrotasks: what creates them (setTimeout, I/O, UI events), the one-per-tick processing rule, and using them deliberately to yield control back to the browser during long-running work.

Total XP: 0|💻 javascript XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

Does the event loop process every pending macrotask back-to-back before doing anything else, the way it does with microtasks?


🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Macrotasks (often just called "tasks") are the lower-priority queue behind setTimeout, setInterval, and browser-driven events like clicks and I/O — understanding how the event loop processes exactly one per tick is key to writing responsive, non-blocking code.

1Macrotasks | JavaScript Tutorial - In-Depth Guide Part 1

A macrotask is a unit of work the event loop processes one at a time — setTimeout/setInterval callbacks, I/O completion, and UI events like clicks all become macrotasks.

+
setTimeout(() => console.log('macrotask 1'), 0);
setTimeout(() => console.log('macrotask 2'), 0);
// Each runs in a SEPARATE event loop tick, not back-to-back
localhost:3000
📥

The Lower-Priority Queue

2Macrotasks | JavaScript Tutorial - In-Depth Guide Part 2

Because only one macrotask runs per tick (with rendering and microtask draining happening in between), the browser gets regular opportunities to repaint and respond to input between macrotasks — which is exactly what keeps a page responsive.

+
function processInChunks(items, chunkSize) {
  function processNext() {
    const chunk = items.splice(0, chunkSize);
    chunk.forEach(processItem);
    if (items.length > 0) setTimeout(processNext, 0); // yield between chunks
  }
  processNext();
}
localhost:3000

Yielding Between Chunks

3Macrotasks | JavaScript Tutorial - In-Depth Guide Part 3

setTimeout(fn, 0) doesn't guarantee the callback runs after exactly 0ms — browsers commonly clamp minimum delays (historically 4ms for nested timeouts), and the callback still waits its turn as a macrotask behind any pending microtasks.

+
setTimeout(() => console.log('runs "immediately", but not really 0ms'), 0);
localhost:3000

'0ms' Isn't Exact

4Macrotasks | JavaScript Tutorial - In-Depth Guide Part 4

User interactions (clicks, key presses, scroll) are also processed as macrotasks — this is why heavy synchronous work between two macrotasks can make a page feel unresponsive to clicks during that window.

+
button.addEventListener('click', () => {
  heavySynchronousComputation(); // blocks all other macrotasks, including new clicks
});
localhost:3000

UI Events Are Macrotasks Too

5Macrotasks | JavaScript Tutorial - In-Depth Guide Part 5

Deliberately splitting long-running work into multiple macrotasks (via setTimeout, or the newer scheduler.postTask API) is a direct, practical technique for keeping a page responsive during heavy processing.

+
async function processLargeArray(items) {
  for (let i = 0; i < items.length; i++) {
    processItem(items[i]);
    if (i % 100 === 0) {
      await new Promise((resolve) => setTimeout(resolve, 0)); // yield
    }
  }
}
localhost:3000

Yielding for Responsiveness

6Step-by-Step Breakdown

A macrotask is a unit of work the event loop processes one at a time — setTimeout/setInterval callbacks, I/O completion, and UI events like clicks all become macrotasks.

Checkpoint: Does the event loop process every pending macrotask back-to-back before doing anything else, the way it does with microtasks?

  • Yes, all macrotasks drain fully like microtasks do
  • No, it processes exactly one macrotask per tick

Because only one macrotask runs per tick (with rendering and microtask draining happening in between), the browser gets regular opportunities to repaint and respond to input between macrotasks — which is exactly what keeps a page responsive.

setTimeout(fn, 0) doesn't guarantee the callback runs after exactly 0ms — browsers commonly clamp minimum delays (historically 4ms for nested timeouts), and the callback still waits its turn as a macrotask behind any pending microtasks.

User interactions (clicks, key presses, scroll) are also processed as macrotasks — this is why heavy synchronous work between two macrotasks can make a page feel unresponsive to clicks during that window.

Checkpoint: If a click handler runs 500ms of heavy synchronous computation, can the browser process another click during that time?

  • Yes, clicks are always processed immediately regardless
  • No, the main thread is blocked until that macrotask finishes

Deliberately splitting long-running work into multiple macrotasks (via setTimeout, or the newer scheduler.postTask API) is a direct, practical technique for keeping a page responsive during heavy processing.

Next, we'll explore 'Memory Leaks'.

Level Up 🚀

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Yielding to the Main Thread Keeps Keyboard Navigation Responsive During Heavy Work

Breaking long-running work into multiple macrotasks ensures keyboard-driven navigation and focus changes (critical for many assistive technology users) remain responsive throughout, rather than freezing entirely until a large synchronous operation completes.

SEO Implications

  • 1

    Chunking Long Tasks Directly Improves Interaction to Next Paint

    Google's Core Web Vitals specifically penalize "long tasks" that block the main thread for extended periods; splitting heavy work into multiple macrotasks is a direct, practical technique for improving this metric and the associated search ranking signal.

Best Practices

Break Up Long Synchronous Work into Multiple Macrotasks

Chunking heavy computation with setTimeout (or similar yielding techniques) between chunks keeps the page responsive to user input and rendering during long operations.

Don't Rely on setTimeout(fn, 0) for Precise Sub-Millisecond Timing

Browsers clamp minimum timer delays and macrotask processing is subject to queueing behind other work; use requestAnimationFrame or dedicated timing APIs when precision actually matters.

Frequent Bugs

THE BUG

A synchronous loop processing thousands of items in one macrotask, causing the page to become completely unresponsive to clicks and scrolling until it finishes.

THE FIX

Break the loop into chunks, yielding control back to the browser (via setTimeout or a similar mechanism) between chunks so other macrotasks, including UI events, get a chance to run.

THE BUG

Assuming `setTimeout(fn, 0)` executes in exactly 0 milliseconds, and building timing-sensitive logic that depends on that assumption.

THE FIX

Treat 0ms as "as soon as possible, but not guaranteed exact", and use a more precise timing mechanism (like requestAnimationFrame or performance.now()-based checks) if exact timing genuinely matters.

Real-World Examples

Processing a Large Dataset Without Freezing the UI

A data import feature needed to process tens of thousands of records client-side without making the page unresponsive during the operation.

async function importRecords(records) {
  const CHUNK_SIZE = 200;
  for (let i = 0; i < records.length; i += CHUNK_SIZE) {
    const chunk = records.slice(i, i + CHUNK_SIZE);
    chunk.forEach(processRecord);
    updateProgressBar(i / records.length);
    await new Promise((resolve) => setTimeout(resolve, 0)); // yield to the browser
  }
}

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

A page freezing during a long synchronous operation

await new Promise(resolve => setTimeout(resolve, 0)); // yield point

The Solution //

Break the operation into chunks processed across multiple macrotasks, yielding between them.

Lesson Glossary

[01]Macrotask (Task)

A unit of work — setTimeout, I/O, UI events — that the event loop processes one at a time.

Code Preview
setTimeout callback

[02]One-Per-Tick Processing

The event loop rule that only one macrotask runs before checking for rendering and draining microtasks.

Code Preview
single task per cycle

[03]Yielding to the Main Thread

Deliberately splitting long work into multiple macrotasks so the browser can render/respond between them.

Code Preview
setTimeout chunking

[04]Timer Clamping

Browsers enforcing a minimum delay (historically ~4ms for nested timers) rather than an exact requested delay.

Code Preview
min delay clamp

[05]Blocking the Main Thread

Running long synchronous code that prevents any other macrotask (including UI events) from being processed.

Code Preview
heavy sync loop

Continue Learning