Skip to content

The Browser

What a Browser Is (Mental Model)

A browser is not just a viewer; it is a runtime with its own OS-like responsibilities. It loads untrusted code, isolates it, executes it, and renders output safely. Every frontend behavior is shaped by constraints imposed by the browser, not frameworks.

Processes, Threads, and Isolation

Modern browsers use a multi-process architecture. A typical setup includes a browser process, renderer processes (per site or tab), GPU processes, and network processes. This isolation limits damage from crashes or exploits. JavaScript runs in a renderer process, usually on a single main thread. Backend engineers often underestimate how strict this isolation is.

Parsing: From Bytes to Structures

When a browser receives HTML, it parses it incrementally into a DOM tree. CSS is parsed into a CSSOM in parallel. JavaScript can block parsing unless marked async or defer. DOM and CSSOM must both be ready before rendering can proceed. This is why script placement and loading strategy matter.

Rendering Pipeline (How Pixels Appear)

Rendering is a staged pipeline, not a single operation. DOM + CSSOM → Render Tree → Layout → Paint → Compositing. Layout calculates sizes and positions; paint fills pixels; compositing assembles layers on screen. Some changes trigger full layout, others only repaint or composite. Performance optimization means avoiding expensive stages.

JavaScript Execution and the Main Thread

JavaScript executes on the main thread of the renderer. This same thread also handles layout, paint coordination, and user input. Long-running JS blocks rendering and input. There is no true parallel DOM access. Web Workers exist, but they cannot touch the DOM.

The Event Loop (Scheduling Model)

The event loop is the browser’s scheduler. Tasks (timers, IO, events) and microtasks (Promises, mutation observers) are queued separately. Microtasks always run before rendering. Rendering happens between task executions when the main thread is free. Misunderstanding this causes race conditions and jank.

Networking Inside the Browser

Browsers implement HTTP, caching, compression, and connection reuse internally. Requests are asynchronous and subject to security rules. Headers like Origin, Cookie, and Authorization are controlled by the browser, not your code. Fetching data is constrained by CORS and credential policies. Frontend networking is opinionated and restrictive by design.

Storage and Persistence

Browsers expose multiple storage mechanisms: cookies, localStorage, sessionStorage, IndexedDB, and Cache Storage. Each has different lifetimes, sizes, and security models. Cookies are sent automatically with requests; others are not. Storage is scoped by origin. Backend-style assumptions about persistence do not hold.

Browser storage is:

  • Scoped by origin (scheme + host + port)
  • Controlled and enforceable by the browser, not your application
  • Designed for untrusted environments
  • Optimized for different access patterns, not general-purpose persistence

Unlike backend storage:

  • Data can disappear unexpectedly (user action, eviction, privacy mode)
  • Capacity is limited and browser-dependent
  • Access may be synchronous or asynchronous (important for performance)
  • Security boundaries are strict and non-negotiable

Never assume browser storage is durable, reliable, or always available.

Cookies

Small key–value pairs managed by the browser and attached automatically to HTTP requests. Cookies are primarily a * network mechanism*, not a storage API. They are tightly integrated with HTTP, headers, and browser security rules.

Key properties

  • Sent automatically with matching requests
  • Size: ~4 KB per cookie (very small)
  • Scoped by domain/path
  • Optional attributes: Secure, HttpOnly, SameSite, Max-Age/Expires

Best used for

  • Session identifiers
  • Authentication tokens (with HttpOnly + Secure)
  • Server-managed state references

Avoid using cookies for

  • Large data (inefficient and truncated)
  • Client-only state
  • Anything frequently updated
  • Sensitive data without HttpOnly

Common pitfalls

  • Forgetting SameSite → CSRF vulnerabilities
  • Storing JWTs without HttpOnly → XSS exfiltration
  • Assuming cookies are private to JS (they’re not unless HttpOnly)

Security note

localStorage

A synchronous, string-only key–value store exposed to JavaScript. Data persists across page reloads and browser restarts until explicitly cleared.

Key properties

  • Scope: origin
  • Lifetime: persistent
  • Size: ~5–10 MB (browser-dependent)
  • Access: synchronous (blocks main thread)

Best used for

  • User preferences (theme, language)
  • Feature flags
  • Non-sensitive cached UI state

Avoid using localStorage for

  • Sensitive data (readable by any JS on the page)
  • Large datasets
  • High-frequency writes
  • Anything performance-critical

Common pitfalls

  • Blocking the main thread with large reads/writes
  • Assuming data is encrypted or protected
  • Using it as a database

sessionStorage

Similar to localStorage, but scoped to a single browser tab. Data is cleared when the tab or window is closed.

Key properties

  • Scope: origin + tab
  • Lifetime: per tab session
  • Size: similar to localStorage
  • Access: synchronous

Best used for

  • Wizard or multi-step form state
  • Temporary UI state tied to a single tab
  • Navigation-related data

Avoid using sessionStorage for

  • Cross-tab communication
  • Long-lived data
  • Anything needing persistence after reload

Common pitfalls

  • Assuming refresh clears it (it doesn’t)
  • Assuming new tab shares it (it doesn’t)

Design note

sessionStorage models “tab-local memory”, not user persistence.

IndexedDB

An asynchronous, transactional, object-store database built into the browser. IndexedDB is the only serious client-side database.

Key properties

  • Scope: origin
  • Lifetime: persistent (subject to eviction)
  • Size: tens to hundreds of MB
  • Access: asynchronous (non-blocking)
  • Supports indexes, cursors, transactions

Best used for

  • Large structured datasets
  • Offline-first applications
  • Client-side caching of API responses
  • Background synchronization data

Avoid using IndexedDB for

  • Small, simple key–value preferences
  • Data needing server authority
  • Extremely latency-sensitive paths (complex API)

Common pitfalls

  • Overcomplicated schema design
  • Poor error handling
  • Treating it like a relational DB

Cache Storage (Cache API)

A storage mechanism designed for HTTP request/response pairs, commonly used with Service Workers. It stores entire responses, not arbitrary objects.

Key properties

  • Scope: origin (and controlled by Service Worker)
  • Lifetime: persistent (subject to eviction)
  • Access: asynchronous
  • Integrated with fetch semantics

Best used for

  • Offline caching of static assets
  • API response caching
  • Performance optimization (avoid network)

Avoid using Cache Storage for

  • Arbitrary application state
  • Data requiring complex querying
  • Anything not naturally request/response-based

Common pitfalls

  • Forgetting cache invalidation
  • Serving stale data incorrectly
  • Treating it like a database

Comparison table

StorageLifetimeSizeAccessSent with requestsBest for
CookiesConfigurable~4 KBAutomaticYesAuth, sessions
localStoragePersistent~5–10 MBSynchronousNoPreferences
sessionStorageTab lifetime~5–10 MBSynchronousNoTab state
IndexedDBPersistentLargeAsynchronousNoOffline data
Cache StoragePersistentLargeAsynchronousNo (manual)HTTP caching

Eviction and reliability (often missed)

  • Browsers may delete data under storage pressure
  • Private/incognito modes behave differently
  • Mobile browsers are aggressive about eviction
  • No hard guarantees of durability

Rule:

  • Never treat browser storage as authoritative
  • The server must remain the source of truth

Security summary

  • Cookies: vulnerable to CSRF if misconfigured
  • localStorage/sessionStorage: vulnerable to XSS
  • IndexedDB: vulnerable to XSS but harder to exploit at scale
  • Cache Storage: can leak stale or sensitive responses if misused

Frontend storage security is about minimizing exposure, not eliminating risk.

Practical backend-engineer rules

  • Use cookies only when the server must see the data
  • Use localStorage/sessionStorage only for low-risk UI state
  • Use IndexedDB for serious client-side data needs
  • Use Cache Storage strictly for HTTP caching
  • Assume storage can disappear at any time
  • Treat XSS as a total compromise of client storage

Security Boundaries (Why Browsers Are Paranoid)

The browser enforces the Same-Origin Policy. Scripts cannot freely read or write cross-origin data. Sandboxing prevents filesystem and OS access. Security decisions are centralized in the browser, not libraries. Most frontend security bugs come from accidentally weakening these boundaries.

Rendering vs Logic: Why UI Feels “Slow”

Perceived performance depends on responsiveness, not throughput. A 50ms block on the main thread is noticeable. Layout thrashing, excessive DOM updates, and synchronous JS degrade UX. Browsers optimize aggressively but only if you follow their rules. Backend intuition about performance often misleads here.

Browser Engines

A browser engine is a large collection of subsystems that together turn web standards into executable behavior. It is not just a renderer; it includes parsing, execution, scheduling, layout, painting, networking hooks, storage, and security enforcement. Standards describe what should happen, but engines decide how. These implementation choices directly affect performance, memory usage, and edge-case behavior. When something behaves differently across browsers, it is almost always due to engine-level differences.

Core subsystems inside an engine

Every modern engine contains roughly the same major components:

  • HTML parser → builds the DOM incrementally
  • CSS engine → parses CSS, computes styles, resolves cascade
  • JavaScript engine → parses, JIT-compiles, and executes JS
  • Layout engine → computes element geometry
  • Paint & compositing → produces pixels and layers
  • Event loop & scheduler → decides what runs when
  • Security enforcement → origin checks, sandboxing, permissions

Engines differ mainly in algorithms, heuristics, and tradeoffs, not in high-level structure.

JavaScript engines (important distinction)

Each browser engine embeds a JavaScript engine:

  • Chromium-based browsers use V8
  • Safari uses JavaScriptCore
  • Firefox uses SpiderMonkey

These engines implement the same ECMAScript spec, but differ in:

  • JIT strategies
  • Garbage collection behavior
  • Optimization heuristics
  • Memory pressure handling

Result: the same JS code can have very different performance characteristics across browsers.

Rendering and layout differences

Layout and rendering are the most engine-specific areas.

  • CSS Grid, Flexbox, and layout edge cases may behave differently
  • Font rendering, subpixel rounding, and text metrics vary
  • Reflow and repaint costs differ significantly
  • Layer promotion heuristics are engine-specific

This is why “works fine on Chrome” is meaningless without cross-engine testing.

Scheduling and responsiveness

Engines decide:

  • When rendering occurs relative to JS
  • How aggressively to throttle timers
  • How background tabs behave
  • How input events are prioritized

Example differences:

  • Some engines are more aggressive about main-thread yielding
  • Some throttle background work earlier
  • Some delay rendering more eagerly to batch work

These decisions affect perceived performance and responsiveness.

Networking behavior

While HTTP standards are shared, engines implement:

  • Connection pooling strategies
  • HTTP/2 and HTTP/3 prioritization
  • Cache eviction policies
  • Preload and speculative fetch heuristics

As a result:

  • Request ordering can differ
  • Cache hit/miss behavior varies
  • Performance tuning can be engine-specific

Security enforcement (non-negotiable)

Security rules are enforced by engines, not libraries. Examples:

  • Same-Origin Policy
  • CORS checks
  • Cookie rules (SameSite, Secure)
  • Sandboxing and permission prompts

Engines may differ slightly in:

  • Timing of enforcement
  • Error handling
  • Default restrictions (Safari is often stricter)

Assuming “the browser will let me” is a common backend-to-frontend mistake.

Why engines matter to application developers

You don’t target “the web”; you target multiple engines. Important implications:

  • Performance tuning must be tested per engine
  • Layout bugs may be engine-specific
  • Timing-sensitive code may behave differently
  • Security assumptions must hold across engines

Frameworks smooth differences, but they do not eliminate them.

Practical rules to internalize

  • Standards define intent; engines define reality
  • Performance bugs are often engine-specific
  • Rendering issues ≠ JavaScript issues
  • Security behavior comes from the engine, not your code
  • “Spec-compliant” does not mean “identical behavior”

Backend-engineer takeaway

Think of browser engines as:

  • Multiple independent OS kernels
  • Running the same ABI (web standards)
  • With different schedulers, memory managers, and graphics stacks

Key Takeaways for Backend Engineers

  • The browser is the platform, frameworks are abstractions
  • The main thread is the primary bottleneck
  • Rendering is incremental and expensive
  • Security is enforced externally, not by your code
  • Performance failures are usually scheduling failures