Software Surface
Browser Fundamentals + TypeScript
Conceptual breadth at staff depth. Event loop priority, render pipeline cost, Web Platform APIs, the TypeScript type lattice, and advanced type patterns. SVG diagrams for memory; MDN and W3C links for precision.
- ~[loop] Event Loop stack · µtasks · tasks · rAF
- *[paint] Render Pipeline DOM · CSSOM · layout · composite
- @[platform] Web Platform APIs Fetch · Observers · Workers · Storage
- ![security] Security Model SOP · CORS · CSP · cookies
- $[types] TypeScript Type System lattice · unknown · never · any
- ^[patterns] Advanced TS Patterns conditional · mapped · template literal
JavaScript Event Loop
JS is single-threaded with a host-managed concurrency model. I/O and timers are offloaded to Web APIs; callbacks are enqueued when ready. The loop's priority ordering is what makes async code predictable.
Microtask Queue
Drained completely after every task and after the initial script. No rAF or paint occurs until the microtask queue is empty. Starvation risk: a microtask that enqueues itself loops forever, blocking all I/O and rendering.
- Promise.then
- queueMicrotask
- MutationObserver
- async/await
Task Queue (Macrotasks)
One task dequeued per loop iteration. Multiple distinct task sources exist (timer, I/O, UI events); the UA may interleave them. MessageChannel yields a macrotask without the 4 ms minimum clamp of setTimeout.
- setTimeout
- setInterval
- MessageChannel
- I/O callbacks
requestAnimationFrame
Fires immediately before the browser paints — ideal for all visual mutations. Batched per frame (~16 ms at 60 fps). Pauses when the tab is hidden. Use to batch DOM reads before writes and avoid layout thrashing.
- requestAnimationFrame
- requestIdleCallback
- frame budget
Scheduler API
scheduler.postTask() gives explicit priority control: user-blocking, user-visible, background. scheduler.yield() cooperatively yields a long task back to the event loop without relinquishing priority.
- scheduler.postTask
- scheduler.yield
- TaskController
- Long Tasks API
Browser Render Pipeline
Which pipeline stages a CSS property triggers determines its cost. Properties like transform and opacity on composited layers skip layout and paint entirely — the GPU reassembles pre-rasterized layers. Properties like width invalidate all stages.
Document Object Model
Live tree of nodes. Script mutations trigger style recalc. documentFragment batches insertions off-tree. TreeWalker traverses efficiently. Range works across arbitrary node boundaries. MutationObserver callbacks fire as microtasks.
- Node
- Element
- DocumentFragment
- TreeWalker
- Range
- MutationObserver
CSSOM + Cascade
Style resolution order: origin → cascade layers → specificity → source order. getComputedStyle returns resolved values (forces style flush). @layer adds an explicit origin tier. @property enables typed custom properties with registered syntax.
- @layer
- specificity
- @property
- :is() :where()
- custom properties
Layout (Reflow)
Computes box geometry. Reading layout properties (offsetWidth, getBoundingClientRect) after mutations forces a synchronous reflow. contain: layout limits reflow scope. ResizeObserver fires before paint after each reflow.
- contain: layout
- will-change
- ResizeObserver
- intrinsic sizing
Compositing Layers
transform, opacity, and filter on a promoted layer skip layout and paint — the GPU reassembles pre-rasterized bitmaps. will-change: transform promotes eagerly (memory cost). content-visibility: auto skips layout/paint for off-screen content.
- will-change
- GPU layer
- content-visibility
- isolation: isolate
Web Platform APIs
Fetch API + Streams
Promise-based network I/O. Response.body is a ReadableStream enabling progressive processing. AbortController cancels in-flight requests. Request/Response are structured-cloneable and composable in Service Workers.
- AbortController
- ReadableStream
- TransformStream
- WritableStream
- Response.body
Observer APIs
Four observer types: IntersectionObserver (viewport crossing), MutationObserver (DOM changes — microtask delivery), ResizeObserver (element size changes — before paint), PerformanceObserver (metric entries). All avoid polling.
- IntersectionObserver
- MutationObserver
- ResizeObserver
- PerformanceObserver
Web Workers
True OS-thread parallelism; workers cannot access the DOM. Communication via postMessage (structured clone) or Transferable (zero-copy). SharedWorker shared across browsing contexts. SharedArrayBuffer + Atomics enable wait-free shared memory.
- postMessage
- Transferable
- SharedArrayBuffer
- Atomics
- SharedWorker
Service Workers
Network proxy off the main thread. Lifecycle: install → activate → fetch interception. skipWaiting() + clients.claim() for immediate takeover. Cache API stores Request/Response pairs. Enables offline-first and background sync.
- Cache API
- Background Sync
- Push API
- skipWaiting
- clients.claim
Storage APIs
localStorage / sessionStorage: synchronous, string-only, ~5 MB. IndexedDB: async, structured data, large capacity, transactions, indices, available in workers. StorageManager.estimate() queries available quota. Cache API keyed on Request objects.
- IndexedDB
- localStorage
- StorageManager
- Cache API
- Cookie Store API
WebSocket + SSE
WebSocket: full-duplex binary-capable TCP channel. EventSource: server-push over HTTP, text-only, auto-reconnect, simpler auth model. Use SSE when only server→client streaming is needed; WS for bidirectional. WebTransport over QUIC is the emerging successor.
- WebSocket
- EventSource
- WebTransport
- binary frames
Performance APIs
Core Web Vitals: LCP (Largest Contentful Paint), INP (Interaction to Next Paint, replaces FID), CLS (Cumulative Layout Shift). performance.mark / measure for custom spans. PerformanceObserver streams entries asynchronously.
- LCP
- INP
- CLS
- TTFB
- Long Tasks
- User Timing
ES Modules + Import
type="module" scripts defer by default, run in strict mode, and have lexical scope. import() dynamic import returns a Promise. import.meta.url is the module's own URL. importmap remaps bare specifiers to URLs at HTML level. Module map prevents double-evaluation.
- import.meta
- dynamic import()
- importmap
- modulepreload
Browser Security Model
Same-Origin Policy
Origin = scheme + host + port. Scripts from origin A cannot read responses or DOM of origin B. Applies to fetch, XHR, window.opener, iframe content access, Web Storage, and cookies. The invariant all other mechanisms build on.
- origin
- site
- schemelessly same-site
- opaque origin
CORS
Credentialed or non-simple requests trigger a preflight (OPTIONS). Key headers: Access-Control-Allow-Origin, ACAO-Credentials. Credentials require non-wildcard ACAO. COEP/COOP/CORP enable SharedArrayBuffer and cross-origin isolation.
- preflight
- ACAO
- credentials
- COEP
- COOP
- CORP
Content Security Policy
Allowlist for script, style, connect, img, media, and other resource sources. script-src 'nonce-{x}' is best practice for inline scripts. strict-dynamic propagates trust to dynamically-created scripts. Report-only mode enables incremental rollout via Reporting API.
- nonce
- strict-dynamic
- report-to
- Trusted Types
Cookies + SameSite
SameSite=Strict: no cross-site dispatch. Lax: top-level navigations only (default since Chrome 80). None requires Secure. HttpOnly prevents JS access. The __Host- prefix requires Secure, no Domain attribute, and path=/. CHIPS partitions third-party cookies by top-level site.
- SameSite
- HttpOnly
- __Host-
- CHIPS
- partitioned
TypeScript Type System
TypeScript uses structural (duck) typing — compatibility is determined by shape, not identity. The type system forms a lattice: unknown is the top type (all values are assignable to it); never is the bottom type (no value inhabits it). any escapes the lattice entirely.
unknown — safe top type
unknown requires narrowing before use (type-safe). any escapes the type system entirely — bidirectional assignability. Prefer unknown for external data (JSON, API responses, user input). noImplicitAny bans accidental widening to any.
- unknown
- any
- noImplicitAny
- type assertion
never — bottom type
Inhabited by nothing. Appears at exhaustive checks (all union members handled), in return types of functions that always throw, and as the intersection of contradictory types (string & number). never is assignable to every type — use it to detect missing branches.
- exhaustive check
- assertion function
- unreachable code
Structural Typing
Compatibility is shape-based: T is assignable to U if T has at least all required properties of U with compatible types. Excess property checking applies only at fresh object literal assignment, not at variable assignment — a common source of confusion.
- excess property check
- freshness
- index signatures
- covariance
- contravariance
Type Narrowing
Built-in: typeof, instanceof, in, truthiness, equality. User-defined: x is T type predicate returns a boolean; asserts x is T asserts or throws. Discriminated unions: a shared literal property identifies each variant — TS narrows based on the literal.
- type predicate
- discriminated union
- control flow analysis
- asserts
Utility Types
Standard type transforms: Partial, Required, Readonly, Pick<T,K>, Omit<T,K>, Record<K,V>, Exclude, Extract, NonNullable, ReturnType<F>, InstanceType<C>, Awaited<T>, NoInfer<T>.
- Partial
- Omit
- ReturnType
- Awaited
- NoInfer
satisfies + as const
satisfies T checks a value against a type without widening the inferred type. as const produces readonly literal types. Combined: const p = {...} as const satisfies Record<...> — constraint checking with precise inference preserved. Available since TypeScript 4.9.
- satisfies
- as const
- const type parameters
- const assertions
TypeScript Advanced Patterns
Generics + Inference
Constraints: T extends K restricts T to subtypes of K. Inference sites: TS infers from call sites, conditional clauses, infer. NoInfer<T> blocks inference at a site without removing the constraint. Variance: function params are contravariant; return types covariant.
- constraints
- infer
- NoInfer
- variance
- covariance
- const type param
Conditional Types
infer R captures a type within a conditional branch. Distributivity: over a naked union type parameter, the conditional distributes member-by-member. To prevent distribution, wrap in a tuple: [T] extends [U]. Basis for ReturnType, Awaited, Parameters.
- infer
- distributive
- [T] extends [U]
- deferred
Mapped Types
Iterate over a union of keys. Modifiers: +readonly, -readonly, +?, -?. Key remapping with as: [K in keyof T as Capitalize<string & K>]. Filter keys by returning never from the as clause. Homomorphic mapped types preserve optionality and readonly.
- keyof
- in keyof
- as clause
- modifiers
- homomorphic
Template Literal Types
Construct string types from literal unions. Combine with mapped types to derive event names, accessor keys, CSS property strings. Built-in string intrinsics: Uppercase, Lowercase, Capitalize, Uncapitalize. infer inside template literals matches string patterns.
- Capitalize
- template literal infer
- string union
- intrinsic string types
Declaration Merging
Interfaces merge across declarations; type aliases do not. Module augmentation: declare module 'x' { ... } extends an existing module's types. Global augmentation: declare global { ... }. Commonly used for Express Request, Jest matchers, Vite ImportMeta, and environment variables.
- interface merging
- module augmentation
- ambient declaration
- declare global
tsconfig + Project References
Key flags: strict enables noImplicitAny + strictNullChecks + more. moduleResolution: "bundler" for Vite/esbuild. verbatimModuleSyntax enforces explicit type-only imports. Project references (composite: true + references) enable incremental builds and type-check boundaries across monorepo packages.
- strict
- composite
- references
- verbatimModuleSyntax
- isolatedModules