AlgoPlusAlgoPlus
Learn/Web
Lesson

Inside the Browser

Multi-process architecture, the single main thread, and the event loop that runs microtasks before every render.

10 min read Watch it move Build it

A modern browser is many programs, not one. Open a tab and the operating system is running several cooperating processes, and inside each one a single main thread juggles all the work using an event loop. These two ideas — process isolation for safety, and a single-threaded loop for order — explain almost everything about how a page behaves, including why it sometimes freezes.

Multi-process architecture

Rather than run everything in one process, the browser splits work across isolated processes so a crash or exploit in one can't take down or spy on the others.

  1. 1Browser process — the chrome around the page: the window, tabs, address bar, and overall coordination.
  2. 2Renderer process — runs a single site's page: parsing, layout, painting, and its JavaScript. Typically one per site, each in a sandbox with no direct access to your files or to other sites.
  3. 3GPU process — a shared process that draws the finished layers to the screen, keeping the graphics driver isolated from the rest.
  4. 4Network process (network service) — handles fetching over the wire for every tab in one place.
Why one process per site
Site isolation means a malicious or buggy page is trapped in its own sandboxed renderer. It can crash without taking your other tabs with it, and it can't read the memory of a banking tab next to it. The cost is more memory — each process has overhead — which is the trade the browser makes for security.

The main thread does one thing at a time

Inside a renderer, the page's JavaScript and its rendering share a *single* main thread. Synchronous code runs on the call stack — a pile of function calls, where only the top item runs. Because there's just one thread, JavaScript and screen updates can never truly happen at the same instant: they take turns.

The event loop, macrotasks and microtasks

When the call stack empties, the event loop decides what runs next by a strict rule: drain every microtask, maybe render a frame, then run one macrotask — and repeat. A macrotask is work the browser queues for later: a setTimeout callback, a click handler, a network response; the loop runs *one* per turn. A microtask is higher priority — mainly a resolved promise's .then callback; the loop drains the *entire* microtask queue before moving on.

console.log('A');
setTimeout(() => console.log('D'), 0); // macrotask
Promise.resolve().then(() => console.log('C')); // microtask
console.log('B');
// prints: A, B, C, D

The synchronous lines run first, so A and B print immediately. setTimeout queues D as a macrotask and the promise queues C as a microtask. When the stack empties, the loop drains microtasks first — so C prints before the loop ever reaches the D macrotask. That ordering, A B C D, is the event loop's rule made visible.

Long tasks jank the UI
The browser can only repaint between macrotasks, after microtasks drain. So if one piece of JavaScript runs for 200ms straight, the main thread never returns to the loop — clicks queue up, animations freeze, and the page feels stuck. The fix is to break long work into chunks (or move it to a Web Worker, a separate thread) so the loop gets frequent chances to render and respond.
OperationTimeSpace
One event-loop turn · render opportunity sits between turns1 macrotask + all microtasks
Long synchronous task · no paint, no input handling until it returnsblocks the whole thread
Check yourself
A promise .then and a setTimeout(fn, 0) are both queued while synchronous code runs. Which callback fires first once the stack empties?