Web Fundamentals

How Browsers Work

From URL bar to painted pixels — the critical rendering path, JavaScript engines, event loops, networking, caching, and performance metrics that every web developer should understand.

01 / Critical Rendering Path

From URL to Pixels

When you type a URL and press Enter, the browser kicks off a precise sequence of steps to turn raw bytes into a visible, interactive page. This is the critical rendering path — the minimum work the browser must do before the first pixel appears.

Network Phase
DNS Lookup
TCP Handshake
TLS Negotiation
HTTP Request/Response
Rendering Phase
HTML Parsing
DOM Tree
CSSOM Tree
Render Tree
Layout
Paint
Composite

DOM and CSSOM

The HTML parser reads bytes, decodes characters, tokenizes tags, and builds the DOM (Document Object Model) — a tree of nodes. CSS is parsed in parallel into the CSSOM (CSS Object Model). Neither tree alone is enough to render; the browser merges them into a Render Tree that contains only visible nodes with their computed styles.

Layout, Paint, Composite

Layout (also called "reflow") calculates the exact position and size of every element in the viewport. Paint fills in pixels — text, colors, images, borders, shadows — producing paint records. Compositing takes independently painted layers (e.g., elements with transform, opacity, or will-change) and combines them on the GPU, which is why CSS transforms are cheaper than layout-triggering properties like width or top.

Parser-Blocking Scripts
A <script> tag without async or defer halts HTML parsing entirely. The browser must download, parse, and execute the script before it can continue building the DOM. This is why scripts belong at the bottom of <body> or use defer.
02 / JavaScript Engines

Parsing to Machine Code

Every major browser ships its own JavaScript engine. They all follow the same general pipeline but differ in optimization strategies and JIT compilation tiers.

EngineBrowserInterpreterOptimizing JIT
V8Chrome, Edge, Node.jsIgnitionTurboFan
SpiderMonkeyFirefoxBaseline InterpreterWarp (Ion replacement)
JavaScriptCoreSafari, BunLLIntDFG → FTL
Common JS Engine Pipeline
Source Code
Parser (AST)
Bytecode
Interpreter
JIT (hot paths)

The engine first parses JavaScript into an Abstract Syntax Tree (AST), then compiles it to bytecode for the interpreter. Functions that are called frequently ("hot") get promoted to an optimizing JIT compiler that produces much faster machine code. If the JIT's assumptions break (e.g., a variable changes type), the engine deoptimizes — falls back to the interpreter and tries again.

V8 Internals

V8 uses hidden classes (also called "shapes" or "maps") to give dynamically-typed objects a structure the engine can optimize against. Objects created the same way share the same hidden class, enabling fast property access via fixed offsets instead of dictionary lookups.

Inline caching records the hidden class seen at a property access site. On subsequent calls, if the hidden class matches, V8 skips the lookup entirely — going straight to the cached offset. This is why consistent object shapes matter for performance.

Optimization Tip
Always initialize object properties in the same order. Adding properties in different orders creates different hidden classes, which defeats inline caching and forces V8 into slower "megamorphic" lookups.
// Good — consistent shape
function Point(x, y) { this.x = x; this.y = y; }

// Bad — different property order = different hidden class
const a = {}; a.x = 1; a.y = 2;
const b = {}; b.y = 2; b.x = 1;  // different hidden class from 'a'
03 / The Event Loop

Concurrency in a Single Thread

JavaScript runs on a single thread, yet handles async I/O, animations, and user input without blocking. The secret is the event loop — a continuous cycle that processes tasks in a specific priority order.

Event Loop Cycle
Call Stack
Microtasks
Render
Macrotask

Microtasks vs Macrotasks

Microtasks include Promise.then(), queueMicrotask(), and MutationObserver. After the call stack empties, all queued microtasks run to completion before the browser can paint or process the next macrotask.

Macrotasks include setTimeout, setInterval, requestAnimationFrame, and I/O callbacks. The browser picks one macrotask per loop iteration, then drains microtasks again.

console.log('1 - sync');

setTimeout(() => console.log('4 - macrotask'), 0);

Promise.resolve()
  .then(() => console.log('2 - microtask'))
  .then(() => console.log('3 - microtask'));

// Output: 1, 2, 3, 4
Microtask Starvation
Because all microtasks drain before the next render, an infinite chain of Promise.then() calls will freeze the page. Long-running async work should yield to the main thread using setTimeout or scheduler.yield().

Web Workers

For truly CPU-intensive work, Web Workers run JavaScript on a separate OS thread. They communicate with the main thread via postMessage() and have no access to the DOM. SharedArrayBuffer enables shared memory between workers with Atomics for synchronization — useful for parallel computation, WASM workloads, and off-main-thread data processing.

04 / Browser Networking

Connections, Prefetch, and Service Workers

Browsers are sophisticated HTTP clients with built-in optimizations for reducing latency and reusing connections.

Connection Pooling

Browsers maintain a pool of persistent (keep-alive) TCP connections per origin — typically 6 connections per host in HTTP/1.1. HTTP/2 multiplexes all requests over a single connection, eliminating head-of-line blocking at the HTTP level (though TCP-level HoL blocking remains, which HTTP/3 with QUIC solves).

Resource Hints

preconnect

Establishes TCP + TLS to an origin early. Use for known third-party origins like fonts or analytics.

prefetch

Fetches a resource at low priority for future navigation. Great for next-page assets. Stored in the HTTP cache.

preload

Fetches a resource at high priority for the current page. Use for late-discovered critical assets like fonts or hero images.

dns-prefetch

Resolves DNS for an origin ahead of time. Cheaper than preconnect — good for origins you might need.

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" href="/critical.css" as="style">
<link rel="prefetch" href="/next-page.js">

Service Workers

A Service Worker is a programmable network proxy that sits between the browser and the network. It intercepts fetch events and can serve responses from cache, transform requests, or implement offline-first strategies. Service Workers run on a separate thread, survive page reloads, and are the foundation of Progressive Web Apps (PWAs).

Cache Strategies
Common Service Worker patterns: Cache First (fast, may be stale), Network First (fresh, slower), Stale While Revalidate (fast + fresh in background) — choose based on your content update frequency.
05 / Browser Caching

HTTP Cache Headers

The HTTP cache is the browser's first line of defense against unnecessary network requests. Two mechanisms work together: freshness (is the cached copy still valid?) and validation (has the resource changed on the server?).

HeaderTypeHow It Works
Cache-ControlFreshnessDirectives like max-age=3600, no-cache, immutable. The primary caching mechanism.
ExpiresFreshnessAbsolute date. Legacy — overridden by Cache-Control when both present.
ETagValidationHash of the resource. Browser sends If-None-Match; server returns 304 if unchanged.
Last-ModifiedValidationTimestamp. Browser sends If-Modified-Since; server returns 304 if unchanged.
Cache Decision Flow
Request
In Cache?
Fresh?
Serve from Cache
Stale
Validate (ETag/Last-Modified)
304? Use Cache : Fetch New
Best Practice: Immutable + Hashing
For versioned assets (e.g., app.a1b2c3.js), use Cache-Control: max-age=31536000, immutable. The filename hash ensures cache-busting on updates, and immutable tells the browser to skip revalidation entirely — no conditional requests, no 304s, just instant cache hits.
06 / Web Performance

Core Web Vitals and Optimization

Google's Core Web Vitals are the standardized metrics for measuring real-user experience. They directly influence search ranking and represent loading, interactivity, and visual stability.

LCP

Largest Contentful Paint. Time until the largest visible element renders. Target: < 2.5s. Optimize with preloaded hero images, critical CSS, and fast server response.

INP

Interaction to Next Paint. Latency from user input to visual update (replaced FID in 2024). Target: < 200ms. Reduce with smaller event handlers and yielding to the main thread.

CLS

Cumulative Layout Shift. Total unexpected layout movement. Target: < 0.1. Fix with explicit width/height on images and reserved space for dynamic content.

TTFB (Time to First Byte)

TTFB measures the time from request start to the first byte of the response. It captures DNS, TCP, TLS, and server processing time. While not a Core Web Vital, poor TTFB (> 800ms) makes hitting LCP targets nearly impossible. Optimize with CDNs, edge computing, and server-side caching.

Critical CSS

Extract the CSS needed for above-the-fold content and inline it in <head>. Load the rest asynchronously. This eliminates the render-blocking stylesheet fetch and lets the browser paint immediately after parsing HTML.

<head>
  <!-- Inline critical CSS -->
  <style>
    /* above-the-fold styles only */
    body { margin: 0; font-family: sans-serif; }
    .hero { height: 100vh; background: #1a1a1a; }
  </style>
  <!-- Load rest async -->
  <link rel="preload" href="/full.css" as="style"
        onload="this.rel='stylesheet'">
</head>

Code Splitting

Instead of shipping one massive JavaScript bundle, split code by route or component. Modern bundlers (Webpack, Vite, esbuild) support dynamic import() to load chunks on demand. Combined with tree shaking (removing dead exports), this keeps initial load fast and defers non-critical code.

Performance Checklist
Compress with Brotli (better than gzip for text). Use responsive images with srcset. Lazy-load below-the-fold images with loading="lazy". Set fetchpriority="high" on LCP images. Avoid layout thrashing by batching DOM reads and writes.

Test Yourself

Score: 0 / 10
Question 01
In the critical rendering path, what is built by combining the DOM and CSSOM?
The Render Tree is built by merging the DOM (structure) with the CSSOM (styles). It contains only visible nodes with their computed styles — elements with display: none are excluded.
Question 02
What does V8's "hidden class" mechanism optimize?
Hidden classes give dynamic objects a predictable structure so V8 can access properties at fixed memory offsets instead of doing slow dictionary lookups. Combined with inline caching, this makes property access nearly as fast as in statically typed languages.
Question 03
In the event loop, when do microtasks (like Promise callbacks) execute?
After the current synchronous call stack empties, the engine drains the entire microtask queue before the browser can render or process the next macrotask. This is why Promises resolve before setTimeout(..., 0).
Question 04
What is the key advantage of HTTP/2 over HTTP/1.1?
HTTP/2 multiplexes multiple streams over a single TCP connection, eliminating the HTTP-level head-of-line blocking that limited HTTP/1.1 to 6 parallel connections per origin. UDP-based transport is HTTP/3 (QUIC).
Question 05
Which resource hint fetches an asset at high priority for the current page?
<link rel="preload"> tells the browser to fetch a resource at high priority for use in the current page. prefetch is for future navigations (low priority), and preconnect only sets up the connection without fetching content.
Question 06
When the browser has a stale cached resource with an ETag, what HTTP header does it send to validate?
The browser sends If-None-Match with the cached ETag value. If the server's current ETag matches, it responds with 304 Not Modified (no body), saving bandwidth. If-Modified-Since is the validation header used with Last-Modified timestamps.
Question 07
What does LCP (Largest Contentful Paint) measure?
LCP marks when the largest content element (image, heading, video poster, etc.) in the viewport finishes rendering. The target is under 2.5 seconds. CLS measures layout shifts, and INP measures input-to-paint latency.
Question 08
Why are CSS transforms (e.g., translateX) cheaper than changing properties like width or top?
Properties like transform and opacity only affect the compositing stage — the GPU moves or fades the pre-painted layer. Changing width or top triggers layout recalculation followed by repaint, which is far more expensive.
Question 09
In V8, what happens when the optimizing JIT compiler (TurboFan) encounters a type assumption violation?
When TurboFan's speculative optimizations are invalidated (e.g., a variable assumed to be an integer receives a string), V8 performs a "deoptimization" — discarding the optimized machine code and falling back to Ignition bytecode execution. The function may be re-optimized later with updated type feedback.
Question 10
Which caching strategy serves a cached response immediately while fetching an update in the background?
Stale While Revalidate serves the cached (potentially stale) response immediately for fast load, then fetches a fresh copy from the network in the background to update the cache for next time. It balances speed and freshness.