Browser & Frontend Runtime
What workers are and why they exist
Parse a 5 MB JSON file on the main thread and the page freezes for ~150 ms — dropped frames, dead buttons. The browser’s answer is not “make the thread faster.” It is “give you more threads.”
The main thread problem
The browser has one thread for JavaScript, layout, paint, and input. While it is busy, nothing else runs. Parse a large file, run a crypto hash, or filter a huge dataset on the main thread and every animation and button response stalls for the duration.
The browser’s answer is off-main-thread execution — workers. Four kinds exist, each for a different job:
- Web worker — a second JS thread for CPU-bound work (parsing, crypto, image processing). No DOM, no
window, nodocument. - Service worker — a network proxy sitting between the page and the server. Intercepts requests, serves cached responses, survives page close.
- Worklet — a tiny hook into the browser’s render or audio pipeline (paint worklet, audio worklet). Even more constrained than a worker.
- SharedArrayBuffer — shared memory between threads (not a worker itself, but the escape hatch from message-passing). Gated behind cross-origin isolation headers.
- Web worker startup
- 5–20 ms
- 5 MB JSON parse, main thread
- ~150 ms freeze
- 5 MB JSON parse, web worker
- 0 ms jank
- Frame budget at 60 fps
- 16.67 ms
- Frames dropped by 150 ms parse
- ~9 frames
The office metaphor
The main thread is you at your desk: you answer the phone (input) and update the whiteboard (DOM) yourself. A web worker is a colleague in a back room — you send a big calculation by note, they do it without disturbing your desk, they send the answer back by note. They cannot touch your whiteboard. Only you can.
A service worker is the mailroom: every letter in or out passes through it first, and it can answer some from its own filing cabinet without going outside. It keeps running even when you leave for the day.
Bea uploads a photo to edit. Sven narrates: “Applying the filter is millions of pixel operations — on the main thread, a 600 ms freeze. So we post the image data to a web worker. The worker churns the pixels on its own thread; the main thread stays free. It posts the result back and we paint it.”
The fundamental rule
Workers have no DOM. A worker’s global is WorkerGlobalScope — it has no document, no window, no elements. This is not a limitation to work around; it is the design. It keeps the DOM single-owner and race-condition-free. A worker can compute what to render and post the result back, but the main thread must apply it.
The one exception: OffscreenCanvas. A canvas bitmap is not part of the DOM tree — it is just a pixel buffer. You can transfer an OffscreenCanvas to a worker, and the worker can draw into it entirely off the main thread. Everything else in the DOM stays main-thread only.
A page hands a heavy calculation to a web worker. Drag the steps into the order they happen.
- 1 Main thread creates the worker: new Worker('calc.js')
- 2 Main thread sends input data: worker.postMessage(data)
- 3 Worker runs the calculation on its own thread
- 4 Worker sends the result back: postMessage(result)
- 5 Main thread's onmessage handler receives the result
Why can't a web worker update the page directly?
What makes a service worker different from a web worker?
In the office metaphor, the colleague in the back room does heavy calculations for you and reports back by note, but cannot touch your whiteboard. Which kind of worker is that?
A page parses a 5 MB JSON file on the main thread and it takes roughly 150 ms. The frame budget at 60 fps is 16.67 ms. Roughly how many frames does that one parse drop?
Why this works
Workers were introduced because JavaScript is single-threaded by design — sharing mutable state across threads requires locks, and locks in a UI context cause deadlocks and priority inversion. The worker model avoids all of that by banning shared state entirely. Each worker has its own heap, its own globals, its own event loop. Data crosses the boundary only through postMessage (which copies it) or through explicit shared-memory primitives (SharedArrayBuffer + Atomics). The constraint is the safety guarantee.
- 01What is the fundamental difference between a web worker and a service worker?
- 02Why do workers have no DOM access?
- 03What is the one DOM-related exception for workers?
The browser’s main thread is the single owner of JS execution, layout, paint, and input — anything blocking it stalls the whole page. Workers provide additional threads: web workers for CPU-bound computation (no DOM, communicates via postMessage), service workers for network proxying (intercept fetch, serve cache, survive page close). Neither can touch the DOM — that constraint is what makes them safe. A 150 ms JSON parse on the main thread drops ~9 frames at 60 fps; moved to a web worker, it costs the main thread nothing.
appears again in41
- Federation and lookahead: batching beyond DataLoadermiddle
- Senior GraphQL API: scheduling contract, tenant isolation, observabilitysenior
- Lock and single-flight: bounding concurrent rebuildsmiddle
- Stale-while-revalidate and CDN request coalescingmiddle
- Detecting stampedes and designing TTL for productionmiddle
- Metastable failure, fencing tokens, and production postmortemssenior
- What a relation is: tables, rows, keys, and constraintsjunior
- Constraints, keys, and Postgres data typesmiddle
- JSONB, arrays, and when a side table winsmiddle
- Schema integrity: deferral, versioning, and production failure modessenior
- Where data fetching happens — and why it decides LCPjunior
- React Server Components and Suspense streamingmiddle
- Senior internals: RSC payload, caching layers, and production failure modessenior
- The IP envelopejunior
- Reading the IP headermiddle
- What TLS does and why it existsjunior
- Key schedule, SNI, ALPN, and extensionssenior
- 0-RTT defenses, ECH, hybrid PQ, and production TLSsenior
- The twelve layers: one URL, seven actorsjunior
- Resilience: cascading retries, circuit breakers, and error budgetssenior
- What is OpenTelemetry: API, SDK, Collector, OTLPjunior
- OTel signals, Semantic Conventions, and the OTLP wire formatmiddle
- The OTel Collector: receivers, processors, exporters, and deployment patternsmiddle
- Vendor neutrality, eBPF instrumentation, the Operator, and browser/serverless OTelsenior
- Operating the OTel Collector: reliability, version skew, failure modes, and governancesenior
- What is trace propagation and why broken propagation is worse than nonejunior
- traceparent and tracestate: the W3C header format in fullmiddle
- Baggage and async boundaries: carrying context across queues and callbacksmiddle
- Async context per language, service mesh, B3 migration, and securitysenior
- Production propagation failures, span links, and platform designsenior
- The debugging funnel: SLO → RED → trace → profilejunior
- OTel architecture: one SDK, four signals, one wire formatmiddle
- The incident loop: from pager to postmortem to preventionmiddle
- Scale, security, and the ROI of observable systemssenior
- At-most-once, at-least-once, exactly-once: the three delivery contractsjunior
- Consumer-side dedup: the cheapest path to exactly-once processingmiddle
- Exactly-once in production: impossibility proof, hybrid patterns, and real incidentssenior
- What OAuth is and why passwords are not the answerjunior
- Authorization code flow with PKCEmiddle
- Sender-constrained tokens: DPoP and mTLSsenior
- OAuth in production: audience attacks, observability, and real failuressenior