By the end of this lesson, you will be able to explain why long JavaScript tasks block the browser main thread and use asynchronous scheduling such as setTimeout(() => {}, 0) to keep the page responsive.
What it is
Asynchronous programming is a way to schedule work so it happens later, after the current JavaScript task finishes. In the browser, JavaScript runs on the main thread, which also handles clicks, input, animations, and rendering. If one function runs for too long, the browser cannot process those other duties until the function returns.
The mental model is a single worker with a to-do list. The current task must finish before the worker can pick up the next queued task. setTimeout does not pause the current task; it adds a callback to the task queue. Related terms include the event loop, call stack, task queue, microtasks, promises, and long tasks.
Why it matters
- Keeps buttons, scrolling, and typing responsive.
- Prevents the browser from showing “page not responding” warnings.
- Lets animations and timers continue while background work is scheduled.
- Makes it possible to process large data sets in small chunks.
- Improves perceived performance by showing progress instead of freezing.
Syntax or steps
The smallest useful pattern is to move the blocking part into a callback:
setTimeout(() => {
// work that should happen after the current task
}, 0);
Steps:
- Do quick setup work synchronously, such as updating the UI.
- Call
setTimeoutwith a callback and a delay of0. - Let the current function return so the browser can handle events and rendering.
- The browser later runs the callback when the call stack is empty.
Example
const items = Array.from({ length: 100000 }, (_, i) => i);
let index = 0;
function processChunk() {
const start = Date.now();
while (index < items.length && Date.now() - start < 10) {
items[index] = items[index] * 2;
index++;
}
if (index < items.length) {
setTimeout(processChunk, 0);
} else {
console.log("Done processing all items.");
}
}
processChunk();
Part by part:
Array.fromcreates a large array to simulate real work.processChunkdoes only about 10 milliseconds of work per call.- The
whileloop stops when time runs out or all items are processed. setTimeout(processChunk, 0)schedules the next chunk after the current task ends.- Because each chunk returns quickly, the browser can handle clicks, scrolling, and rendering between chunks.
Common mistakes
- Thinking
setTimeout(() => {}, 0)runs immediately. It runs after the current task finishes, so a long synchronous loop before it still blocks the page. - Using
asyncandawaitaround a CPU-heavy loop and expecting it to become non-blocking. The loop still runs on the main thread unless work is chunked or moved to a worker. - Forgetting to update progress or finish state when using repeated
setTimeoutcalls. - Choosing too large a chunk size. If each chunk takes hundreds of milliseconds, the UI still freezes.
When to use it
Use setTimeout chunking when you need to split main-thread work into smaller tasks. Use a Web Worker when the work is CPU-bound and can run off the main thread. Use requestAnimationFrame when the work is tied to visual updates.
| Technique | Best for | Limitation |
|---|---|---|
setTimeout(() => {}, 0) | Yielding briefly to the browser and processing data in chunks. | Still runs on the main thread. |
| Web Worker | Heavy calculations that should not block the UI. | Cannot directly access the DOM. |
requestAnimationFrame | Animation and visual updates. | Not ideal for arbitrary background work. |
Practice
Guided exercise: Write code that logs "before", schedules setTimeout(() => console.log("after timeout"), 0), then runs a loop that takes about one second, and finally logs "after loop". Predict the order.
Expected output: before, after loop, after timeout.
Challenge: Process an array of 50,000 numbers by adding 1 to each item in chunks of 5,000 using setTimeout. Hint: keep an index variable and schedule the next chunk until index reaches the array length.
Quick check
Question: Why does setTimeout(() => {}, 0) not make a long synchronous loop non-blocking?
Answer: Because the loop is part of the current task. The timeout callback is queued and cannot run until the loop finishes and the call stack is empty.
Summary
Asynchronous scheduling gives the browser a chance to handle user interaction and rendering between pieces of work. setTimeout(() => {}, 0) is a simple way to yield, but it does not magically move work off the main thread.