completefrontend
Open lab My progress
THE WEB PERFORMANCE FIELD GUIDE

Processes and threads

The browser, renderer, GPU, and network processes, and why almost everything you write runs on one main thread. Measured in Chrome: processes, threads, a blocked page and a worker.

By the end, you can
  • 01
    Name the processesSay what the browser process, the renderers, the GPU process and the network service each do, and why each site gets its own renderer.
  • 02
    Know the main thread's jobsList the jobs a page's main thread does, one at a time, and explain why one long task holds all of them up.
  • 03
    Move work off itPick compositor-only animations, Web Workers and OffscreenCanvas for work that doesn't need the main thread.

One browser, many processes

Asha opens the Leaf and Kettle page in Chrome on her laptop. It's one tab. The page shows the shop's name, a box of reviews, and a small map.

The reviews and the map are iframes. An iframe is a page shown inside another page. The reviews come from the shop's own website. The map comes from a different website. In the course's copy of the page, a second address stands in for the map's website.

To Asha, it's one page. Inside Chrome, it's many separate jobs. Something has to fetch the files, run the shop's JavaScript, draw the pixels, and answer her clicks. So who does all that work? Is Chrome one program, doing one job after another? And if one part gets stuck, does everything get stuck?

The course loaded this page in Chrome 156 on a Mac, and asked Chrome where each part of it ran:

Where the page and its iframes ranPop out in the code editor (opens in a new tab)JavaScript
// Where Chrome ran the Leaf and Kettle page and its iframes.// Each number is a process ID: the number the system gave that process.const ranIn = { "the page": 78121, "the reviews": 78121, "the map": 78120 }; const renderers = new Set(Object.values(ranIn));const iframes = Object.keys(ranIn).length - 1;console.log(`The page and its ${iframes} iframes ran in ${renderers.size} renderer processes.`);
  • Lines 1 and 2 are comments. A process is one running program. The operating system gives each process a number, its ID.
  • Line 3 holds what Chrome recorded. The page and the reviews ran in process 78121. The map ran in process 78120.
  • Line 5 puts the IDs in a Set. A Set keeps each value only once, so this one holds two IDs.
  • Line 6 counts the iframes: three parts, minus the page itself, makes two.
  • Line 7 prints The page and its 2 iframes ran in 2 renderer processes.

Here is Asha's tab, with a line from each part of it to the process it ran in:

Asha's tab, showing the Leaf and Kettle page: its heading, then the reviews iframe, then the map iframe. The page, from 127.0.0.1, ran in process 78121. The reviews, from 127.0.0.1, ran in process 78121. The map, from localhost, ran in process 78120. Chrome also ran these processes: Browser, GPU, Network service, Storage service.Leaf and KettleThe Leaf and Kettle page: its heading, a reviews box saying "Five stars for the mint tea.", and a map box saying "12 Kettle Lane".127.0.0.1127.0.0.1localhostthe pagethe reviewsprocess 78121the mapprocess 78120Chrome also ranBrowserGPUNetwork serviceStorage service
Asha's one tab, and the process each part of it ran in. The page and its reviews, both from 127.0.0.1, shared process 78121. The map, from localhost, got process 78120. Along the bottom are the other processes Chrome ran. The screen is from a later load of the same page in a narrow window, made just for this picture; the process IDs are from the recording above.

So one tab used two programs. A process that shows web pages is called a renderer, because to render a page is to draw it. The page and its reviews shared one renderer. The map, from another website, got a renderer of its own.

And those two weren't all. Chrome also ran a process for its own window, one for the screen, and one for the network.

Inside each process, the work is split again, into threads. A thread lives inside a process and runs part of its program. It does one thing at a time. Several threads in one process can work at the same time, and they share that process's memory. Here is the whole picture:

Chrome showing the Leaf and Kettle page. At the top, the browser process: the tabs, the address bar and your clicks. Below it are four parts. The shop's renderer process holds a main thread, a compositor thread and worker threads. The map's renderer process has its own main thread and compositor thread. Then come the GPU process and the network service.Browser processtabs, address bar, your clicksmain threadcompositorworker threadsshop's renderermain threadcompositormap's rendererGPU processNetwork serviceand more servicesthe page and its reviewsthe map, from another site
Each box is a process: a program with its own memory. Each card inside a renderer is a thread. The page and its reviews share one renderer. The map comes from another site, so it gets a renderer of its own.
Real-life analogySeparate kitchens in one building

Picture a big building with several restaurants inside. Each restaurant has its own kitchen, with its own cooks and its own recipes. One night, a pan catches fire in one kitchen. That kitchen closes, but the other restaurants keep serving dinner. And no cook can walk into another kitchen and read its recipes.

In real life: The building
In the browser: Chrome
In real life: A kitchen, with its own room and its own recipes
In the browser: A process, with its own memory
In real life: One kitchen for each restaurant
In the browser: One renderer process for each site
In real life: The cooks in a kitchen
In the browser: The threads in a process
In real life: A fire that closes one kitchen
In the browser: A crash that stops one renderer, and only that one
The main idea

Chrome is many programs, called processes. On a computer, each site gets a renderer process of its own, so one site can't crash or read another. Inside a renderer, one main thread runs your JavaScript and works out styles, layout and paint, one job at a time. Other threads help. They put frames together, turn the page into pixels, and compile scripts. Keep the main thread free, and the page answers the person quickly.

The rest of the lesson takes this picture apart, one piece at a time:

  • the processes, and why each site gets a renderer of its own;
  • the main thread, and the many jobs it does one at a time;
  • the compositor thread, which keeps the screen moving while the main thread is busy;
  • the work you can move off the main thread.

The JavaScript lesson Browser architecture and the system design lesson Inside the browser tell the same story in brief. This lesson measures it in Chrome.

The multi-process browser

Chrome starts several processes before it shows any page at all. The course started Chrome with no page open, and asked it what it was running:

Chrome before any pagePop out in the code editor (opens in a new tab)JavaScript
// What Chrome was running before any page was open.const before = ["browser", "GPU", "network service", "storage service", "notifications"]; const renderers = before.filter((type) => type === "renderer");console.log(`Before any page, Chrome ran ${before.length} processes.`);console.log(`${renderers.length} of them were renderers.`);
  • Line 1 is a comment.
  • Line 2 holds the five processes Chrome listed, by type.
  • Line 4 keeps only the renderers.
  • Lines 5 and 6 print Before any page, Chrome ran 5 processes. and 0 of them were renderers.

No page, no renderer. Renderers came only once there was a page to show. Each of the other processes does one job, for the whole browser:

  • The browser process runs everything around the page: the tabs, the address bar, the back and forward buttons, and the bookmarks. It starts the other processes and coordinates them.
  • The GPU process works with the GPU, the computer's graphics chip, to put pictures on the screen. It runs on its own because the GPU takes drawing requests from many apps and shows them all on one screen.
  • The network service fetches files from the internet, for every tab.
  • Two smaller services finish the list: one for storage, and one for the Mac's notifications.
Real-life analogyThe front desk runs the building

Back to the building of restaurants. At the entrance there's one front desk. It opens the doors, answers the phones, and takes deliveries for every restaurant. When a guest walks in, the desk sends them to the right restaurant. The kitchens never go out to the street themselves.

In real life: The front desk
In the browser: The browser process: the tabs, the address bar and the buttons
In real life: Sending each guest to the right restaurant
In the browser: Passing a click to the renderer of the page it landed on
In real life: Answering the phones and taking deliveries
In the browser: The network service, which fetches files for every tab
In real life: Kitchens that never go out to the street
In the browser: Renderers, which Chrome keeps away from your files

Here is that desk at work. When Asha clicks, the browser process gets the click first. It only knows where on the screen the click landed. So it sends the click on to the renderer showing that page, and the renderer runs the page's click handler. Mariko Kosaka follows a touch along this same path in Inside look at modern web browser, her series on Chrome's developer site.

One renderer for each site

Now back to the question from the start. Why did the map get its own renderer, when the reviews shared the page's?

Because Chrome gives each site a renderer of its own. A site is a scheme, such as https, plus a main domain name. https://example.com is one site. So www.example.com and shop.example.com belong to the same site. This rule is called Site Isolation, and it covers iframes too.

The reviews came from the shop's own site, so they shared the page's renderer. The map came from another site, so it got its own. In the course's copy, the page came from 127.0.0.1 and the map from localhost. Both names lead to the course's own computer. But they're different names, so Chrome treated them as different sites. That's the separate kitchens from the start: one for each restaurant.

The first picture showed the processes Chrome was running once the page had loaded. Chrome listed four renderers in all at that moment, and the page used two of them. It also listed a tracing service, which ran because the course was recording a trace.

Why does Chrome go to all this trouble? There are three reasons, and one cost:

  • A crash stays in one place. If a renderer crashes or hangs, only its pages stop. Kosaka puts the other case plainly: "If all tabs are running on one process, when one tab becomes unresponsive, all the tabs are unresponsive."
  • A sandbox. The operating system lets Chrome limit what a process may do. Renderers run whatever code a site sends, so Chrome keeps them away from your files.
  • Sites can't read each other. One process can't see another's memory. With each site in its own renderer, a harmful site can't reach another site's data in memory. Site Isolation has been on by default on desktop since Chrome 67.
  • The cost is memory. Each process keeps its own copy of shared parts, such as V8, Chrome's JavaScript engine. So Chrome limits how many processes it starts, based on your device's memory and CPU. On Android, only some sites get a process of their own, such as sites you log in to.

Why it matters: on a computer, a site that crashes or freezes in another tab leaves Asha's page working. But inside her page's renderer, most of the work still depends on one thread. That thread is next.

The main thread's many jobs

Step inside the shop's renderer. Most of the work there happens on one thread, the main thread. In Kosaka's words, "the main thread handles most of the code you send to the user."

Real-life analogyOne head cook

In each kitchen, one head cook makes every dish. She reads the order, cooks it, plates it, and only then starts the next one. She never makes two dishes at once. So if one dish takes ten minutes, every order behind it waits ten minutes.

In real life: The head cook
In the browser: The main thread
In real life: One dish
In the browser: One task, like running a script or working out the layout
In real life: Never two dishes at once
In the browser: One task at a time
In real life: Orders waiting behind a slow dish
In the browser: A click handler waiting behind a long task

Here are the main thread's jobs:

  • It reads the HTML and builds the DOM, the tree of the page's elements.
  • It reads the CSS and works out each element's styles.
  • It runs your JavaScript: scripts, timers, promise callbacks, and event handlers such as a click handler.
  • It works out the layout: where each element goes, and how big it is.
  • It works out the paint: a list of what to draw, and in what order.
  • It does part of the garbage collection: clearing out memory the page no longer uses.

Each piece of work the main thread picks up is a task. It does one task at a time, like the head cook with one dish.

The Leaf and Kettle page is tiny, so its main thread had little to do. To watch a main thread at work, the course loaded a real site: its own home page, completefrontend.com. It loaded the page five times and kept the middle load. Then it added up what the main thread did, until one second after the page finished loading:

Where the main thread's time wentPop out in the code editor (opens in a new tab)JavaScript
// The course's home page: what its main thread did, in ms.const work = { parsing: 5.8, script: 131, layout: 78.6, paint: 33.3, other: 57.9 }; const busy = Object.values(work).reduce((sum, ms) => sum + ms, 0);console.log(`The main thread was busy for ${busy.toFixed(1)} ms.`);for (const [job, ms] of Object.entries(work)) {  console.log(`${job}: ${Math.round((ms / busy) * 100)}% of that time`);}
  • Line 1 is a comment.
  • Line 2 holds the recorded time for each kind of job, in milliseconds (ms). A millisecond is a thousandth of a second.
  • Line 4 adds them up. Line 5 prints The main thread was busy for 306.6 ms.
  • Lines 6 to 8 print each job's share of that time, one per line: parsing: 2% of that time, script: 43% of that time and layout: 26% of that time. Then come paint: 11% of that time and other: 19% of that time.

The five names on line 2 group the trace's work like this:

  • parsing is reading the HTML and the CSS.
  • script is running JavaScript. It includes compiling scripts and clearing out unused memory.
  • layout includes working out styles.
  • paint includes decoding images and handing the result to the compositor.
  • other is the rest of the time inside the main thread's tasks.

So where did the main thread's time go? Running JavaScript took the biggest share, 43%. Styles and layout came next, at 26%. On this page, scripts are the first place to look for a less busy main thread.

Why it matters: all of these jobs share one thread. While a script runs, nothing else on that list can happen. No layout, no paint, and no click handler. One long task holds up everything behind it, like the slow dish in the kitchen. The next lesson looks at that line of waiting tasks in detail.

The compositor and raster threads

So what happens when the main thread is stuck on a long script? Does the whole page freeze? Not all of it. Part of the screen can keep moving, thanks to a second thread in the renderer: the compositor thread.

To see how, follow the page after the main thread has worked out the paint. Kosaka describes these steps:

  1. The main thread splits the page into layers: parts of the page that are drawn separately, like sheets of tracing paper stacked on top of each other.
  2. It commits the layers to the compositor thread. To commit is to hand them over.
  3. The compositor thread cuts each layer into tiles, small squares, and sends the tiles to raster threads.
  4. Raster threads turn each tile into pixels. That's called rasterizing. The pixels go into the GPU's memory.
  5. The compositor thread puts the tiles together into a frame: one full picture of the page. The frame goes on to the GPU process, which puts it on the screen.

The screen shows a new frame many times a second, like the pages of a flip book. Here is the hand-off in the home page's load, with how long each thread was busy:

Inside the renderer, recorded while the course's home page loaded. The main thread, busy 306.7 ms, commits its work to the compositor thread, busy 20.2 ms. The compositor hands tiles to worker threads to raster, busy 139.0 ms in all. The GPU process's main thread, busy 161.3 ms, puts the frame on the screen.main thread306.7 msJavaScript, style, layoutcompositor20.2 msframes, scrollingworker threads139.0 msraster 37.3 msGPU process161.3 msdraws the framecommittilesdrawing commands
How long each thread was busy while the course's home page loaded, until one second after its load event. In Chrome's trace, raster ran on the renderer's worker threads, which also compiled scripts.
Real-life analogyA waiter carries the plates out

Back in the kitchen, a waiter carries the plates out to the tables. When the head cook is stuck on a hard dish, the waiter keeps working. He can carry out plates that are already made, and slide them along the table. But if a guest wants a dish made differently, that waits for the cook.

In real life: The waiter
In the browser: The compositor thread
In real life: Plates that are already made
In the browser: Layers the main thread has already handed over
In real life: Carrying a plate out, or sliding it along the table
In the browser: A new frame: scrolling, or moving a layer with transform
In real life: A dish the guest wants made differently
In the browser: A change that needs layout, like moving a box with left

The layers are already turned into pixels. So to scroll, the compositor thread shows a different part of them. To move a layer, it puts the layer in a new place in the next frame. Neither needs the main thread. In Kosaka's words, the compositor thread "does not need to wait on style calculation or JavaScript execution."

Only some changes work this way. Paul Lewis, writing on web.dev, names the CSS properties the compositor can change on its own. In his words: "Today there are only two properties for which that is true - transforms and opacity." A change to the layout, like a new left, needs the main thread for every frame.

The course tested this with a page of two boxes. Both slide to the right, the same distance at the same speed. The green box moves with transform. The orange box moves with left. One second in, a script keeps the main thread busy for one more second. Here are five frames Chrome drew during that busy second:

  1. The green box, moved with transform, at 150 pixels. The orange box, moved with left, at 147 pixels.

    At 1,030 mstransform 150 px, left 147 px

  2. The green box, moved with transform, at 187 pixels. The orange box, moved with left, at 147 pixels.

    At 1,280 mstransform 187 px, left 147 px

  3. The green box, moved with transform, at 222 pixels. The orange box, moved with left, at 147 pixels.

    At 1,513 mstransform 222 px, left 147 px

  4. The green box, moved with transform, at 260 pixels. The orange box, moved with left, at 147 pixels.

    At 1,763 mstransform 260 px, left 147 px

  5. The green box, moved with transform, at 295 pixels. The orange box, moved with left, at 147 pixels.

    At 1,997 mstransform 295 px, left 147 px

Five frames Chrome drew while a script kept the main thread busy, from 1,008 ms to 2,008 ms. The green box, moved with transform, kept sliding right. The orange box, moved with left, stayed where it was.

The course read where each box was from the frames' pixels. Here are the first and the last frame of the busy second:

How far each box moved while the main thread was busyPop out in the code editor (opens in a new tab)JavaScript
// Two boxes sliding right while a script kept the main thread busy.// When Chrome drew each frame (ms), and where each box was (px).const firstFrame = { ms: 1030, transform: 150, left: 147 };const lastFrame = { ms: 1996.5, transform: 295, left: 147 }; console.log(`The transform box moved ${lastFrame.transform - firstFrame.transform} px.`);console.log(`The left box moved ${lastFrame.left - firstFrame.left} px.`);
  • Lines 1 and 2 are comments. Each time counts from when the page started loading.
  • Line 3 is the first frame, at 1030 ms. Line 4 is the last, at 1996.5 ms, almost a second later.
  • Line 6 prints The transform box moved 145 px.
  • Line 7 prints The left box moved 0 px.

The transform box kept sliding through the busy second. The compositor thread moved it, without the main thread. The left box froze until the script ended, because each of its frames needed layout. And Chrome never stopped drawing. It drew just as many frames in the busy second as in the calm second before it.

Scrolling works the same way. Where the page has no input listeners, the compositor thread scrolls it on its own. Where it has some, the compositor sends the input to the main thread first, and waits. A listener marked passive tells it not to wait. The listener still runs, but the scroll goes ahead.

Why it matters: animate transform and opacity, and an animation keeps going while a script runs. Animate a property that changes the layout, like left, and it stops whenever the main thread is busy. Try it in your own browser:

Playground: block the main thread
Two boxeslive
  • transform: translateX()
  • left
The main threadlive
Boxesstill
Last blocknone yet
Frames Chrome drew in its busy second59
Try it yourself
What happened

Start the boxes. Then block the main thread for a second, and watch which box keeps moving.

This runs in your browser. The block button runs a loop that keeps the main thread busy for 1000 ms, like a long script. Both boxes travel the same distance in the same time. Only the property that moves them is different.

What can run off the main thread

The compositor isn't the main thread's only help. The renderer also keeps a pool of worker threads for jobs that don't need the page itself. These are Chrome's own threads, and you don't start them. In the trace, the raster work ran on them, for example.

Real-life analogyHelpers in the back

The head cook has helpers in the back of the kitchen. They chop the vegetables and make the sauces. They never serve a table. They hand what they made to the cook, and the cook puts it on the plate.

In real life: The helpers in the back
In the browser: Worker threads
In real life: Chopping vegetables and making sauces
In the browser: Work that doesn't need the page: raster, compiling scripts, a photo filter
In real life: Handing the bowl to the cook
In the browser: A Web Worker sending its result back to the main thread
In real life: Helpers never serve a table
In the browser: A worker can't change the DOM

How much work did the worker threads take off the main thread on the home page? Here is the renderer's busy time:

The renderer's threadsPop out in the code editor (opens in a new tab)JavaScript
// The home page's renderer: how long each kind of thread was busy, in ms.const busy = { "main thread": 306.7, "worker threads": 139, "compositor thread": 20.2 }; const total = Object.values(busy).reduce((sum, ms) => sum + ms, 0);const offMain = total - busy["main thread"];console.log(`The renderer's threads were busy for ${total.toFixed(1)} ms in all.`);console.log(`${Math.round((offMain / total) * 100)}% of that was off the main thread.`);
  • Line 1 is a comment.
  • Line 2 holds how long each kind of thread was busy, from the same trace as before.
  • Line 4 adds them up. Line 5 takes away the main thread's part.
  • Line 6 prints The renderer's threads were busy for 465.9 ms in all.
  • Line 7 prints 34% of that was off the main thread.

So a third of the renderer's work never touched the main thread. Two jobs took most of the worker threads' time. They rastered tiles for 37.3 ms. And they compiled and optimized JavaScript for 44.1 ms.

The main thread still parsed and compiled scripts for 47.2 ms. V8's Addy Osmani explains why. Most of that work can happen on a background thread, but "some work still has to happen on the main thread."

So the browser already moves what it can. The table shows what can run off the main thread. Read each row across: the work, the thread that does it, and what you can do.

What can run off the main thread
What work?Which thread does it?What should you do?
Scrolling, and animations of transform and opacityThe compositor thread.Animate only transform and opacity. Mark touch listeners as passive.
Turning tiles into pixelsRaster threads, then the GPU process.Nothing. The browser does it for you.
Parsing and compiling scriptsBackground threads in V8, for most of it.Nothing. V8 starts as the bytes arrive.
Your own heavy JavaScriptA Web Worker.Start one with new Worker(), and send it work with postMessage(). It can't touch the DOM.
Drawing on a canvasA worker, with OffscreenCanvas.Hand the canvas to the worker with transferControlToOffscreen().

Your own heavy work: Web Workers

Say Leaf and Kettle adds a page where Asha can put a filter on a photo of her tea. The filter is plain JavaScript, so it runs on the main thread. While it runs, the page can't answer Asha's taps.

A Web Worker is a thread you start yourself, for your own JavaScript. MDN calls workers "a simple means for web content to run scripts in background threads." Two rules come with them:

  • A worker can't change the DOM. Only the main thread changes the page.
  • The page and the worker talk by sending messages, with postMessage(). A message's data is copied, not shared.

The course tested a worker on a small page. 400 ms of busy work stands in for the filter. 100 ms after the work starts, the course taps the page's button: a real mouse click on it. It did this five times with the work on the main thread, and five times with it in a worker. Each time, the button's click handler wrote down how long the tap had waited:

How long the tap waitedPop out in the code editor (opens in a new tab)JavaScript
// How long each of five taps waited for its click handler, in ms.const waited = {  "on the main thread": [298.2, 298.3, 298.4, 300.8, 303.9],  "in a worker": [0.8, 0.8, 0.9, 1, 1],}; for (const [where, taps] of Object.entries(waited)) {  const middle = [...taps].sort((a, b) => a - b)[2];  console.log(`With the filter ${where}, the middle tap waited ${middle} ms.`);}
  • Line 1 is a comment.
  • Line 3 holds the five waits with the filter on the main thread. Line 4 holds the five with it in a worker.
  • Line 8 sorts each list and picks the third of the five: the middle value, called the median.
  • Line 9 prints With the filter on the main thread, the middle tap waited 298.4 ms. and With the filter in a worker, the middle tap waited 0.9 ms.

Same filter, same tap. On the main thread, the tap waited about 300 ms: the rest of the filter's run. In a worker, the main thread was free, and the click handler ran in under a millisecond.

Why it matters: the worker didn't make the filter faster. It took 400 ms either way. What changed is that the main thread stayed free to answer Asha. Try it with your own taps:

Playground: move the work to a worker
The filter in a workerJavaScript
// page.js: send the photo to a worker, and show the result when it comes back.const worker = new Worker("filter-worker.js", { type: "module" });worker.onmessage = (event) => showPhoto(event.data);worker.postMessage(photo); // filter-worker.js: the filter runs here, on the worker's own thread.onmessage = (event) => postMessage(filter(event.data));
The tapslive
Filter rannot yet
Filter tooknot yet
Taps0
Longest waitnone yet
Try it yourself
Your taps

Run the filter on the main thread or in a worker. Then tap the button while it runs. The panel times how long each tap waited for its click handler.

This runs in your browser. The filter is 400 ms of busy work, standing in for a photo filter. The worker runs the same code as the recorded page's worker.js. Each tap's wait runs from its event's timeStamp, when the browser got the tap, to its click handler.

The code in the playground shows both sides of a worker:

  • Line 2 starts a worker. It runs the file filter-worker.js on a thread of its own.
  • Line 3 says what to do when the worker sends a message back: show the filtered photo.
  • Line 4 sends the photo to the worker.
  • Line 7 is the worker's side. When a message arrives, it runs the filter and sends the result back with postMessage().

Two features here have support boxes. A module worker, the type: "module" on line 2, runs its file as a JavaScript module, so the file can use import. OffscreenCanvas lets a worker draw on a canvas.

The JavaScript lesson How workers work shows what a worker costs to start, and to talk to.

Main thread, or another thread?

Every piece of work below runs on some thread. Some of it needs the main thread. The rest can happen on another thread. Sort each one:

Main thread, or another thread?
  • Your click handler
  • Layout after an element's width changes
  • Reading the HTML into the DOM
  • An animation of left
  • An animation of transform
  • Scrolling a page whose touch listeners are all passive
  • Turning tiles into pixels
  • Code in new Worker("filter.js")
Try it yourself
0 of 8 correct

Sort each piece of work by the thread that does it.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "JavaScript is single-threaded, so the browser is too." No. A page's JavaScript runs on one main thread, but Chrome uses many threads. Loading the home page kept 23 of them busy.
  • "Each tab gets one process." No. Each site gets its own renderer, iframes included. Asha's one tab used two renderers, because the map came from another site.
  • "A worker can update the page." No. A worker can't change the DOM. It sends its result back, and the main thread changes the page.
  • "Every CSS animation keeps going when the main thread is busy." No. Only animations of transform and opacity do. The box moved with left froze.
  • "A busy main thread only delays JavaScript." No. It delays styles, layout, paint and click handlers too. A tap waited 298.4 ms behind the filter.
  • "A worker makes the work faster." No. The filter took 400 ms either way. The worker kept the main thread free.

Practice exercises

Answer first, then use the hints.

Exercise 1 · Warm-upA third site

Leaf and Kettle adds a third iframe: a booking form from a booking website. What does the example print now?

Starter codePop out in the code editor (opens in a new tab)JavaScript
// Where Chrome ran the Leaf and Kettle page and its iframes.
// Each number is a process ID: the number the system gave that process.
const ranIn = { "the page": 78121, "the reviews": 78121, "the map": 78120, "the booking form": 78125 };

const renderers = new Set(Object.values(ranIn));
const iframes = Object.keys(ranIn).length - 1;
console.log(`The page and its ${iframes} iframes ran in ${renderers.size} renderer processes.`);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFaster layout

    The home page's layout gets twice as fast: 39.3 ms instead of 78.6. What does the first line print now?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    // The course's home page: what its main thread did, in ms.
    const work = { parsing: 5.8, script: 131, layout: 39.3, paint: 33.3, other: 57.9 };
    
    const busy = Object.values(work).reduce((sum, ms) => sum + ms, 0);
    console.log(`The main thread was busy for ${busy.toFixed(1)} ms.`);
    for (const [job, ms] of Object.entries(work)) {
      console.log(`${job}: ${Math.round((ms / busy) * 100)}% of that time`);
    }

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFrames in the busy second

      The boxes playground's panel shows how many frames Chrome drew during the course's busy second. How many was it?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeHow much sooner

        With the filter in a worker, how much sooner did the middle tap's click handler run?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeThe main thread in DevTools

          In DevTools' Performance panel, what is the track for the page's main thread called?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeChrome's processes

            Which window in Chrome lists every process it's running, with how much memory and CPU each one uses?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask two things: which process does the work, and then which thread?

              The processes and threads quiz · 8 questionsScore: first tries count
              1. Question 1 of 8How many renderer processes does the iframes example count?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                // Where Chrome ran the Leaf and Kettle page and its iframes.
                // Each number is a process ID: the number the system gave that process.
                const ranIn = { "the page": 78121, "the reviews": 78121, "the map": 78120 };
                
                const renderers = new Set(Object.values(ranIn));
                const iframes = Object.keys(ranIn).length - 1;
                console.log(`The page and its ${iframes} iframes ran in ${renderers.size} renderer processes.`);

                Choose an answer to see the explanation.

              2. Question 2 of 8Why did the map iframe get a renderer process of its own?

                Choose an answer to see the explanation.

              3. Question 3 of 8Asha clicks a button on the Leaf and Kettle page. Which process gets her click first?

                Choose an answer to see the explanation.

              4. Question 4 of 8What does the last line of the boxes example print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                // Two boxes sliding right while a script kept the main thread busy.
                // When Chrome drew each frame (ms), and where each box was (px).
                const firstFrame = { ms: 1030, transform: 150, left: 147 };
                const lastFrame = { ms: 1996.5, transform: 295, left: 147 };
                
                console.log(`The transform box moved ${lastFrame.transform - firstFrame.transform} px.`);
                console.log(`The left box moved ${lastFrame.left - firstFrame.left} px.`);

                Choose an answer to see the explanation.

              5. Question 5 of 8Which CSS properties can the compositor thread animate without the main thread?

                Choose an answer to see the explanation.

              6. Question 6 of 8What can't a Web Worker do?

                Choose an answer to see the explanation.

              7. Question 7 of 8In the recording, why did the tap wait so long with the filter on the main thread?

                Choose an answer to see the explanation.

              8. Question 8 of 8What do raster threads do?

                Choose an answer to see the explanation.

              Key takeaways

              • Chrome is many programs, called processes: a browser process, a GPU process, services such as the network service, and renderers.
              • On a computer, each site gets a renderer of its own, iframes included. A crash, or a harmful site, stays inside its own process.
              • The cost is memory, so Chrome limits how many processes it starts.
              • Inside a renderer, one main thread runs your JavaScript, styles, layout and paint, one task at a time.
              • The compositor thread puts frames together from layers that are already drawn. Scrolling, and animations of transform and opacity, can keep going while the main thread is busy.
              • Worker threads raster tiles and compile scripts. A Web Worker runs your own heavy JavaScript.
              • A worker doesn't make work faster. It keeps the main thread free for the person.

              Remember the one-liner.
              Almost everything you write runs on one main thread. Keep it free, and let the other threads do the rest.

              Sources

              The facts on this page come from these docs and articles:

              Coming next: The event loop and frames, also in How the browser loads and renders. How the main thread picks its next task, when the browser gets a chance to draw, and why a long task freezes everything.

              CompleteFrontend Clear concepts. Working examples.