Learning path · Level 13 · Tutorial 1

Understand Browser Tasks and Rendering

Explain when browser work can become visible and why JavaScript can delay interaction even when an interface uses asynchronous functions.

  • Level: Intermediate
  • Duration: 12 min
  • 7 steps
Start tutorial Back to the path
Understand Browser Tasks and Rendering

Step 1 of 7

Tasks Take Turns on the Main Thread

Predict why synchronous JavaScript keeps queued event and timer callbacks waiting on the main thread.

Tasks do not interrupt each other

The current task runs first

One task keeps the main thread

When JavaScript is executing synchronously on the main thread, the current task runs to completion. A click callback or timer callback that is waiting does not interrupt that JavaScript.

An event listener is work the browser can select as a task. If its handler performs a long calculation, other eligible tasks must wait—including later input and timer callbacks. Browser rendering work can also be delayed while that task occupies the main thread.

A queued callback cannot interrupt

A left-to-right timeline shows a click handler occupying the main thread while an eligible timer callback waits until the handler ends.

A zero-delay timer can become eligible during a handler, but its callback is queued task work and waits for the current task to finish.

Tip

Keep the model precise

This timeline is a useful simplified model, not a promise that every browser task source uses one universal first-in, first-out queue. The important rule here is narrower: a queued event or timer callback does not interrupt synchronous JavaScript that is already running on the main thread.

Schedule now, run later

Zero delay is not immediate

A delay of 0 means that the timer callback is eligible later; it does not mean “run inside the current function.” In this trace, the synchronous statements finish before the browser can select the timer callback as another task.

Trace a timer

Run this function from a click handler or the browser console and inspect the log order.

javascript
function runDemo() {
  console.log('start');
  setTimeout(() => console.log('timer'), 0);
  console.log('end');
}

runDemo();
Expected output
start
end
timer

The timer callback runs after the current synchronous function has returned. Its exact elapsed delay is not established by this trace.

Check the scheduling idea

Schedule the callback

Which line schedules a timer callback as later task work rather than running the callback immediately?

Apply the task rule

Predict the waiting work

During a click-handler task, this function runs:

function runDemo() {
  console.log('start');
  setTimeout(() => console.log('timer'), 0);
  doSynchronousWork();
  console.log('end');
}

Which statement correctly describes what must wait?

Step 2 of 7

Place Microtasks Between Tasks

Trace how synchronous statements, promise reactions, and timer callbacks are ordered across a task and its microtask checkpoint.

From a task to its microtasks

Promise reactions use the microtask queue

A promise reaction is a microtask once its promise has settled. During a task, synchronous JavaScript keeps running; the reaction does not run in the middle of those statements.

After the current task finishes its synchronous work, this tutorial’s short examples reach a microtask checkpoint. The browser runs queued microtasks there before selecting the next task. A timer callback that is eligible to run still waits for that checkpoint.

Where the reaction fits

Diagram showing a current task finish, then a microtask checkpoint containing a promise reaction, followed by an eligible timer task.

A settled promise reaction runs at the microtask checkpoint after the current task’s synchronous work.

Trace one task, one microtask, and one timer

A promise reaction comes before the timer

This short trace schedules one promise reaction and one zero-delay timer. You can trace its order without assigning an exact elapsed time.

javascript
console.log('start');
Promise.resolve().then(() => console.log('promise'));
console.log('end');
setTimeout(() => console.log('timer'), 0);
Expected output
start
end
promise
timer

The ordering is what matters; actual timing is not implied by these lines.

Drain the checkpoint before another task

A checkpoint drains newly queued microtasks too

A microtask checkpoint drains the microtasks that are queued for it. If one microtask queues another microtask, the new one is added to that same checkpoint and runs before the browser selects a later task.

For the examples here, queued microtasks run in enqueue order. This is an execution-order model, not a measurement of how many milliseconds each step takes.

Tip

Keep the queues separate

setTimeout(..., 0) makes the timer callback eligible for a later task. It does not let that callback jump ahead of microtasks that are waiting at the current checkpoint.

Practice: place the additional microtask

Trace a nested microtask

Order the console events produced by this script. The first promise reaction queues one additional microtask.

javascript
console.log('start');

Promise.resolve().then(() => {
  console.log('promise');
  Promise.resolve().then(() => console.log('promise'));
});

console.log('end');
setTimeout(() => console.log('timer'), 0);

Order the complete trace

Put the console events in their execution order.

  1. Console: start
  2. Console: end
  3. Console: timer
  4. Second console: promise (queued by the first reaction)
  5. First console: promise

Step 3 of 7

Separate DOM Changes from Visible Frames

Change state before the screen changes

State changes are not pixels

A DOM or style mutation updates browser state right away. For example, setting statusEl.textContent changes the element’s text in the DOM.

That does not mean the pixels on screen have already changed. The browser may need to update its rendering work later. Until a new visible frame is produced, the learner can still be looking at the previous frame.

Keep these facts separate:

  • JavaScript changed browser state.
  • A future browser rendering update may show that state.

DOM state and the displayed frame

A diagram distinguishes several status changes in browser state from one later visible frame that shows only the final Done status.

Multiple state changes can happen before the browser displays another frame.

Rendering has opportunities, not guarantees

Where a visible update may fit

A task does not come with a guaranteed paint at its end. After the current task finishes, the browser reaches the microtask checkpoint. In the examples in this tutorial, pending microtasks run before a possible rendering update.

Only then can there be a rendering opportunity. “Opportunity” matters: browser rendering is not promised after every task, statement, or DOM mutation.

If more than one mutation happens before an update, one later visible frame can reflect them together.

A possible point for browser rendering

A left-to-right timeline shows one task containing Working and Done mutations, a microtask checkpoint, and then a dashed possible rendering opportunity whose visible frame shows Done.

Both mutations can finish, and queued microtasks can drain, before a possible visible frame reflects the final state.

Logs order JavaScript; they do not show frames

A console trace is not paint evidence

Assume this code runs during a click-handler task and statusEl refers to a status element. The console can establish the JavaScript sequence: synchronous statements run, then the promise reaction runs at the microtask checkpoint.

It cannot establish when pixels changed. Both status mutations can occur before the browser has a rendering opportunity, so Working... may never be a separately visible frame.

Two mutations, one possible later frame

Trace this sequence; do not treat the log as a frame-by-frame record.

javascript
console.log('start');
statusEl.textContent = 'Working...';
Promise.resolve().then(() => console.log('promise'));
statusEl.textContent = 'Done';
console.log('end');

Tip

Keep the evidence separate

A console log proves that JavaScript reached that statement. It does not prove that browser rendering has already displayed the current DOM state. Use the execution trace to reason about ordering, and describe visible updates as possible unless a rendering result is known.

Check your reasoning

Separate what is confirmed from what is possible

A click-handler task contains the code below. Which conclusion is justified?

console.log('start');
statusEl.textContent = 'Working...';
Promise.resolve().then(() => console.log('promise'));
statusEl.textContent = 'Done';
console.log('end');

Step 4 of 7

Why Await Does Not Guarantee a Paint

Fulfilled await: task first, microtask next

A fulfilled promise still resumes quickly

An async function begins on the main thread just like any other function: it runs synchronous statements until it reaches await.

When the awaited promise is already fulfilled, there is no resource-waiting period. The code after await is queued as a microtask. After the current task ends, the browser performs a microtask checkpoint before it selects a later task such as a timer callback.

That ordering does not create a guaranteed visible frame between Working... and the later work.

Timeline: await and a possible frame

A left-to-right timeline shows a click task reaching a fulfilled-promise await, then a microtask continuation and computation before a possible rendering opportunity and a later timer task.

The browser can consider browser rendering only after the microtask checkpoint finishes; the intermediate status is not guaranteed to become a visible frame.

Tip

Keep the two observations separate

Console messages can prove JavaScript order, but they do not prove that a particular DOM state has painted. A rendering opportunity is still only an opportunity, not a promise of immediate pixels.

Async syntax does not move computation away

Trace the handler

This handler places both a promise reaction and an already fulfilled await inside one click task. The promise reaction is queued first, so it runs first at the microtask checkpoint. The continuation then performs synchronous work before the timer can run.

A status update followed by fulfilled await

Script portion for a page containing elements with IDs run and status. The console order is predictable; the moment browser rendering becomes visible is not.

javascript
const runButton = document.querySelector('#run');
const statusEl = document.querySelector('#status');

async function runDemo() {
  console.log('start');
  statusEl.textContent = 'Working...';

  Promise.resolve().then(() => console.log('promise'));
  setTimeout(() => console.log('timer'), 0);

  await Promise.resolve();
  console.log('after await');

  const endTime = performance.now() + 20;
  while (performance.now() < endTime) {
  }

  statusEl.textContent = 'Done';
  console.log('end');
}

runButton.addEventListener('click', runDemo);
Expected output
start
promise
after await
end
timer

This establishes JavaScript execution order. It does not establish that `Working...` was painted before `Done`.

Check the continuation boundary

A busy checkpoint can postpone a frame

A microtask can queue another microtask. The browser keeps draining the microtask checkpoint while more are queued, in enqueue order.

A short, finite chain can therefore postpone input tasks, timer tasks, and a possible rendering opportunity. An infinite chain would make the page unresponsive, so use finite chains only when reasoning about this behavior—not as an executable demonstration.

Complete the fulfilled-promise boundary

Complete the line so the statements that follow continue after a fulfilled promise settles.

javascript
async function runDemo() {
  statusEl.textContent = 'Working...';
  
  statusEl.textContent = 'Done';
}

Apply the scheduling model

Explain the missing loading frame

A click task sets the status to Working..., reaches an await for an already fulfilled promise, and has a timer queued. Its continuation performs a short synchronous calculation, changes the status to Done, and a finite chain of additional microtasks is queued before browser rendering.

Write two sentences: first, place the continuation relative to the click task and timer. Second, explain why neither the fulfilled-promise await nor the microtask chain guarantees that Working... becomes visible.

Write at least 120 characters (0/120).

Step 5 of 7

Follow a Browser Update to the Screen

Follow one visual update

One update, several responsibilities

A visible browser update is a pipeline, not a direct result of one JavaScript statement. After a DOM or style change leaves rendering work pending, browser rendering can prepare a later frame through distinct responsibilities:

  • Style calculation determines which CSS rules and values apply.
  • Layout calculates geometry: sizes and positions.
  • Paint prepares the drawing of text, backgrounds, borders, and other visual content.
  • Compositing combines rendered layers into the displayed frame.

This is a conceptual sequence for following one update. It does not mean every change must repeat every stage, or that all rendering work is one synchronous operation on the main thread.

From pending update to visible frame

A left-to-right diagram follows a web card through Style calculation, Layout, Paint, and Compositing, ending as one visible browser frame.

Each stage has a different output: applicable styles, geometry, drawn content, then an assembled frame.

Avoid an all-or-nothing model

What each stage produces

Use the output of each stage to keep their roles separate. For example, changing a class may require new applicable styles; if those styles change an element's width, its geometry may also need updating before its content can be drawn.

Rendering-stage outputs

The stages are distinct responsibilities, not a fixed cost charged in full for every update.

A conceptual browser rendering pipeline
StagePrimary outputExample question
Style calculationComputed stylesWhich color and display rules apply?
LayoutGeometry boxesWhere is the element, and how large is it?
PaintDrawn visual contentHow are text, border, and background drawn?
CompositingDisplayed frameHow do rendered layers combine on screen?

Tip

Important boundary

Browser rendering can reuse work that has not changed. A change does not automatically require every stage again. Also, some rendering work can occur off the main thread; this diagram does not describe four JavaScript functions running one after another.

Check the pipeline

Match stages to results

Match each browser rendering concept to its result or rule.

Tap an item and then its matching pair.

Step 6 of 7

Distinguish Waiting from Computing

Identify whether elapsed time comes from pending resources, synchronous main-thread computation, or both.

Compare the timelines

Two kinds of elapsed time

An asynchronous resource wait and synchronous computation can both make an operation take time, but they use the browser differently.

When a resource request is pending, that wait does not itself keep the main thread busy. The main thread can handle other eligible work, and the browser can have a rendering opportunity. Neither outcome is guaranteed: unrelated JavaScript or queued work could still occupy the main thread.

By contrast, a synchronous calculation occupies the main thread until it returns. During that interval, input handling and any new visible update whose remaining browser rendering work needs the main thread can be delayed.

Waiting leaves a different timeline than computing

Two left-to-right timelines compare a pending resource wait with a long main-thread computation block.

A pending resource can leave the main thread available for eligible work. A long synchronous computation occupies that lane and can delay updates that need main-thread rendering work.

Locate the switch from waiting to computing

One operation can contain both

An async function can contain both a resource wait and ordinary synchronous JavaScript. The await pauses this function while the request is pending. Once the data is available, the continuation runs and calculateTotals executes synchronously on the main thread.

The async keyword changes how the function delivers its result; it does not make expensive calculations run on a background thread. A synchronous calculation can delay input handling and a new visible update that depends on main-thread rendering work. This does not imply that every rendering-related operation stops: some compositing or other browser rendering work can occur off the main thread.

Resource wait followed by computation

Study the two intervals in this function. It is a code-reading example, not a complete runnable file.

javascript
async function refreshSummary() {
  statusEl.textContent = 'Working...';

  const response = await fetch('/items.json');
  const items = await response.json();
  const total = calculateTotals(items);

  statusEl.textContent = 'Done';
  return total;
}

Tip

Read the boundary

The request can be pending without this function consuming the main thread for the whole wait. After the awaited data is available, calculateTotals(items) does not become asynchronous merely because it appears in an async function.

Judge main-thread availability

Total time is not the whole story

Similar completion times can conceal very different main-thread occupancy. Focus on what occupies the main thread during each interval, not only on the time at which an operation finishes.

Same total time, different availability

Two operations can each finish in about 600 ms while leaving different chances for the browser to process eligible work.

Main-thread availability affects responsiveness even when total completion time looks similar.
OperationElapsed intervalsMain-thread effect
A500 ms resource waiting; 100 ms small updateThe wait itself does not occupy the main thread; eligible work may run during that interval.
B100 ms resource waiting; 500 ms synchronous processingThe processing interval occupies the main thread and can delay input and updates needing main-thread rendering work.
C600 ms synchronous processingThe main thread is occupied for the whole processing interval.

Tip

Keep the distinction precise

Resource waiting is not a guarantee of responsiveness. Another task or a busy microtask checkpoint can still delay input or browser rendering while a request is pending. Conversely, main-thread computation does not mean every rendering-related operation is blocked; some work can proceed off the main thread.

Apply the distinction

Classify the source of delay

Match each scenario to its most accurate classification.

Tap an item and then its matching pair.

Step 7 of 7

Trace an Interaction from Click to Frame

Integrate tasks, microtasks, rendering opportunities, rendering stages, and the difference between waiting and computation in one click-handler scenario.

Connect the complete model

One click: ordered JavaScript, possible pixels

A click starts a task on the main thread. In this handler, JavaScript sets Working..., queues a timer task and a promise reaction, then reaches await Promise.resolve().

The handler’s synchronous portion stops at await, but its continuation is queued as a microtask. At the microtask checkpoint, the promise reaction runs, then the await continuation runs the finite calculation, sets Done, and logs end.

That JavaScript ordering is predictable. Visible pixels are different: after the checkpoint finishes, the browser can have a rendering opportunity. It may update pixels then, but not at a guaranteed exact point relative to later tasks. Therefore, Working... is not guaranteed to become visible before Done.

Click-handler timeline

A left-to-right timeline shows a click task, a microtask checkpoint, a dashed possible rendering opportunity, and a later timer task. A separate visible-frame lane shows Style calculation, Layout, Paint, and Compositing leading to a frame that can show Done.

The log order comes from tasks and microtasks. The dashed rendering point is possible, not proof that an intermediate DOM state has painted.

Verify carefully and contrast the delay

Optional local verification

For an optional local check, create a file named browser-tasks.html, paste this complete document, open it in a browser, open the developer console, and click Run demo once.

The console can confirm JavaScript execution order. It cannot prove when a particular status became visible on screen. The finite loop is synchronous main-thread computation after the fulfilled-promise await; its duration varies by computer.

Task and Frame Demo

Save as browser-tasks.html and open it locally.

html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Task and Frame Demo</title>
  <style>
    body { font-family: sans-serif; padding: 24px; }
    #status { display: block; margin-top: 12px; }
  </style>
</head>
<body>
  <button id="run">Run demo</button>
  <output id="status">Ready</output>

  <script>
    const runButton = document.querySelector('#run');
    const statusEl = document.querySelector('#status');

    async function runDemo() {
      console.log('start');
      statusEl.textContent = 'Working...';
      setTimeout(() => console.log('timer'), 0);
      Promise.resolve().then(() => console.log('promise'));
      await Promise.resolve();
      console.log('after await');

      let total = 0;
      for (let index = 0; index < 5000000; index += 1) {
        total += index;
      }

      statusEl.dataset.total = String(total);
      statusEl.textContent = 'Done';
      console.log('end');
    }

    runButton.addEventListener('click', runDemo);
  </script>
</body>
</html>
Expected output
start
promise
after await
end
timer

The browser console may add source links or formatting, but the five log labels appear in this order. This output does not establish paint timing.

Waiting is not computing

Companion scenario: if a request remains pending after an await, that resource wait does not itself occupy the main thread. The calculation in the demo does. A single async operation can include both intervals.

Compare the delay sources

Compare the two sources of delay. Responsiveness depends on main-thread occupancy, not only on total completion time.

A calculation can delay work needed for a DOM-driven frame without implying that every rendering or compositing operation stops.
Part of an operationMain thread occupancyWhat can happen meanwhile?
Pending resource requestNot occupied merely by waitingOther eligible main-thread tasks and rendering opportunities may occur.
Finite calculation in the handlerOccupied until the calculation finishesQueued input tasks and main-thread-dependent work needed for a new frame wait; some rendering work can be independent.
Processing after a resource settlesOccupied while processing runsThe earlier resource wait does not remove this later main-thread cost.

Trace the guaranteed ordering

Order the console trace

After one click of the local demo, put the five console labels in their JavaScript execution order.

  1. after await
  2. start
  3. end
  4. timer
  5. promise

Final review and application

Apply the complete model

Write a compact explanation of the click scenario.

  1. Give the five console labels in order, separated by arrows.
  2. State whether a frame showing Working... is guaranteed before Done, and explain what await Promise.resolve() does instead of creating a reliable rendering break.
  3. Name the four conceptual browser rendering stages and give each stage’s role.
  4. Contrast the finite loop with time spent waiting for a pending resource.
  5. If you ran the file, report the console observation and state why it is not proof of paint timing.

Write at least 250 characters (0/250).

Complete

Tutorial completed

Congratulations! You completed: Understand Browser Tasks and Rendering

You can now trace tasks and microtasks, treat rendering as a possible browser update rather than a guaranteed pause, and distinguish asynchronous resource waiting from main-thread computation.

Download the App now to have access to + 5000 free courses, exercises, certificates and lots of content without paying anything!

  • 100% free online courses from start to finish

    Thousands of online courses in video, ebooks and audiobooks.

  • More than 60 thousand free exercises

    To test your knowledge during online courses

  • Valid free Digital Certificate with QR Code

    Generated directly from your cell phone's photo gallery and sent to your email

Cursa app on the ebook screen, the video course screen and the course exercises screen, plus the course completion certificate