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
| Storage | Lifetime | Size | Access | Sent with requests | Best for |
|---|---|---|---|---|---|
| Cookies | Configurable | ~4 KB | Automatic | Yes | Auth, sessions |
| localStorage | Persistent | ~5–10 MB | Synchronous | No | Preferences |
| sessionStorage | Tab lifetime | ~5–10 MB | Synchronous | No | Tab state |
| IndexedDB | Persistent | Large | Asynchronous | No | Offline data |
| Cache Storage | Persistent | Large | Asynchronous | No (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