Skip to content
Preptima

Frontend fundamentals

What the browser does between HTML and pixels, the JavaScript semantics behind the classic bugs, why a React re-render happens, what actually moves each Core Web Vital, and the accessibility facts that are requirements rather than polish. Fifty-seven items, twelve worked through with code or a diagram.

57 questions

Go deeper on Frontend Engineering
Standard Sheet View

The browser rendering pipeline

Show me what the browser does between HTML and pixels.

Every performance question in the frontend is really a question about which of these stages you triggered and how often.

The critical path is that the render tree needs both the DOM and the CSSOM, so CSS is render-blocking by definition — the browser will not paint an unstyled page and then restyle it. That is why stylesheets belong in the head and why a large stylesheet delays first paint even when most of it is unused.

Scripts are worse, because a classic <script> blocks parsing entirely: the parser stops, fetches, executes, then resumes, and the executing script may itself read layout, which forces the CSSOM to be complete first. defer keeps the download parallel and runs the script after parsing in document order; async runs it the moment it arrives, in no guaranteed order, which suits an independent analytics tag and breaks anything with a dependency.

The three stages at the end are a hierarchy of cost, and the whole of frontend animation performance follows from it. Changing geometry runs layout, paint and composite. Changing a colour skips layout. Changing transform or opacity skips both layout and paint, so it happens on the compositor and can hit sixty frames per second on a page too heavy to lay out even once.

What is the difference between reflow and repaint?

A reflow — layout — recalculates the geometry of boxes, and because boxes affect their siblings and children it can cascade across a large part of the tree. A repaint fills in pixels for boxes whose appearance changed but whose geometry did not, which is cheaper. Changing width or inserting an element reflows; changing background-color repaints only. The practical rule is that reflow cost scales with how much of the tree is affected, so a change near the root is far more expensive than the same change on a leaf.

Show me layout thrashing and the fix.

The loop below is a well-known way to make a page unusable, and it looks completely reasonable.

// Bad: forces layout on every iteration
for (const el of items) {
  el.style.height = el.offsetHeight + 10 + 'px';
}

Style writes are batched by the browser and flushed later. Reading offsetHeight demands an answer now, so the browser must flush every pending write and run layout before it can respond. With one hundred items that is one hundred synchronous layouts in a single frame — the pattern known as forced synchronous layout, or layout thrashing.

The fix is to separate the phases so all reads happen before any write:

// Read everything first
const heights = items.map((el) => el.offsetHeight);
// Then write, with no interleaved reads
items.forEach((el, i) => { el.style.height = heights[i] + 10 + 'px'; });

Now there is one layout instead of a hundred, because nothing forced a flush in the middle.

The properties that trigger it are worth recognising on sight: offsetTop, offsetHeight, scrollTop, clientWidth, getBoundingClientRect and getComputedStyle. Any of them appearing inside a loop that also writes styles is the bug. And the reason it is hard to spot in a profiler is that the cost attributes to the innocuous-looking read, not to the writes that created the pending work.

What is a stacking context and how does it trap a z-index?

A stacking context is a self-contained layer group: elements inside it are ordered among themselves, and the whole group is placed as one unit within its parent. So a child with z-index: 9999 cannot escape a parent whose own context sits below a sibling — the huge number is compared only against its siblings. Contexts are created by more than position plus z-index: opacity below one, transform, filter, will-change and isolation all create one, which is why adding a fade animation can suddenly break an overlay.

What is critical CSS, and is it worth extracting?

The subset of rules needed to render what is visible before scrolling, inlined into the HTML so first paint does not wait on a stylesheet request. It is worth it when the stylesheet is large and on the critical path, which is most of the time, and the win is a full round trip removed from LCP. The costs are real though: the inlined CSS is duplicated in every HTML response and cannot be cached separately, and the extraction must be automated in the build or it drifts from the actual styles within a week.

What makes a task "long", and why does it matter?

Any block of main-thread work over fifty milliseconds, because the thread is single and nothing else — no click handling, no rendering, no scrolling — happens while it runs. A user who taps during a 300ms task waits at least 300ms for any visual response, which is what INP measures. The causes are large synchronous re-renders, parsing and executing a big bundle, expensive JSON handling, and hydration. The fix is to break the work up so the loop can yield between chunks, or move it off the thread into a worker.

What is the difference between the DOM and the virtual DOM?

The DOM is the browser's actual tree of nodes, where mutations are expensive because they can trigger layout and paint. A virtual DOM is a plain-object description of the tree that a library builds cheaply, diffs against the previous description, and uses to compute a minimal set of real mutations. It is not inherently faster than well-targeted direct manipulation — it is faster than naive re-rendering, and its real value is that it lets you write code as though you redraw everything.