Topics
v6.0.0
On this page · sections

JavaScript Rendering, Performance & Workers

Can you keep the main thread free for input — and know which work belongs in a Worker?

The main thread runs JS, style, layout, and much of paint. A long task is a frozen UI. Performance interviews are event-loop interviews with pixels attached.

These questions test:

  • Critical rendering path
  • Reflow vs repaint vs composite
  • Long tasks and INP
  • requestAnimationFrame
  • Workers, structured clone, transferables

Questions

Rendering

  1. The Rendering Pipeline
  2. Layout Thrashing

Performance

  1. Long Tasks and What to Measure

Workers

  1. Web Workers and postMessage

Senior-Level Follow-ups

  1. Composite-Only Properties
  2. Structured Clone vs Transfer
  3. When Not to Use a Worker

Rendering

1. The Rendering Pipeline

Why This Matters

“Make it faster” is not a plan. You need to know which stage you are hitting.

Problem

How does the browser go from HTML to pixels, and where does JS sit?

Example

HTML → DOM CSS → CSSOM DOM + CSSOM → render tree Layout → geometry Paint → pixels Composite → layers to screen

JS on the main thread can dirty style and layout. After a task and its microtasks, the browser may run rAF, then style/layout/paint.

Interview Answer

“The main thread interleaves JS with rendering. I describe DOM/CSSOM → render tree → layout → paint → composite. I optimize the stage I actually block — usually layout or a long JS task.”

2. Layout Thrashing

Why This Matters

Interleaving writes and reads forces the browser to flush layout repeatedly.

Problem

Why is this slow, and how do you batch it?

Example

el.style.width = "100px"; const h = el.offsetHeight; // forced layout el.style.height = h * 2 + "px";

Reads that force layout include offsetHeight, getBoundingClientRect, clientWidth.

Batch:

read all then write all (or write in rAF)

Interview Answer

“A geometric read after a write flushes layout. I group reads, then writes. For visual updates I schedule requestAnimationFrame so work lands in the same frame.”

Performance

3. Long Tasks and What to Measure

Why This Matters

A 200ms click handler is an INP problem even if Lighthouse LCP looks fine.

Problem

What is a long task, and which metrics would you quote?

Example

Tasks over ~50ms are “long.” They block input and paint.

Metrics:

MetricAsks
LCPWhen the main content appears
INPHow late interactions are
CLSVisual jumps
TBTMain-thread blocking during load

Tools: Performance panel, performance.mark, Event Timing, Long Animation Frames.

Fixes: split work, await between chunks, virtualize lists, move CPU to a Worker, reduce JS parse with code splitting.

Interview Answer

“I treat anything that occupies the main thread as user-visible. I measure INP for interaction, LCP for load, and the Performance profile for the actual function. I do not micro-optimize spreads while a 200ms reducer runs.”

Workers

4. Web Workers and postMessage

Why This Matters

Workers have a separate event loop and no DOM.

Problem

How do you move CPU work off the main thread?

Example

const worker = new Worker(new URL("./worker.js", import.meta.url), { type: "module", }); worker.postMessage({ type: "PARSE", payload: bigString }); worker.onmessage = (event) => { use(event.data); };
self.onmessage = (event) => { const ast = parse(event.data.payload); self.postMessage({ type: "PARSED", ast }); };

Messages are copied with structured clone. Functions, DOM nodes, and some objects cannot be cloned.

Interview Answer

“A Worker is another JS thread with its own loop and no DOM. I send data with postMessage, handle clone limits, and keep the UI thread for input and rendering.”

Senior-Level Follow-ups

5. Composite-Only Properties

transform and opacity can often skip layout and paint, using compositor layers.

Animating top, left, width, or font-size typically costs layout.

Prefer transform for motion. Verify in the Performance panel — “can” is not “always.”

6. Structured Clone vs Transfer

Clone copies. Large ArrayBuffers should be transferred:

worker.postMessage({ buffer }, [buffer]);

Ownership moves. The sender’s buffer is detached.

SharedArrayBuffer is shared memory and needs COOP/COEP headers. Most UI apps never need it.

7. When Not to Use a Worker

Do not Worker-ize tiny work: postMessage latency can exceed the task.

Do not put UI logic in a Worker — no DOM, no window.

Do not share mutable objects; you send copies (or transfers).

Good fits: parse, compress, image decode, crypto, search indexes.

Final Interview Strategy

1. Name the pipeline stage 2. Don't interleave layout reads and writes 3. Long tasks block INP 4. Measure before rewriting algorithms 5. Workers: no DOM, message passing 6. Transfer big buffers 7. Don't pay postMessage for 2ms of work

Senior-level signal: Tie jank to a stage (JS, layout, paint) and a fix (yield, batch, Worker). Vague “use a Worker” without clone costs is not enough.

Last updated on