Users say the page jumps around while it loads, but your synthetic audit reports no layout shift at all. How do you find it and fix it?
Layout shift is a field problem: it depends on network speed, consent banners, ads and personalisation that a lab run never sees. Collect layout-shift entries from real sessions with their source nodes, then reserve space for whatever moved.
What the interviewer is scoring
- Whether the candidate turns to field data with attribution rather than re-running the synthetic audit
- Does the answer explain specifically why a fast, uninstrumented lab run cannot reproduce this
- That space is reserved by declaring intrinsic dimensions rather than by guessing a min-height
- Whether a late web-font swap is recognised as a shift with a font-metrics fix
- Does the candidate separate shifts that follow user input from unexpected ones when reading the data
Answer
Short answer
Layout shift is a field problem: it depends on network speed, consent banners, ads and personalisation that a lab run never sees. Collect layout-shift entries from real sessions with their source nodes, then reserve space for whatever moved.
Keep core web vitals explicit in the answer because that is the concept the interviewer is actually trying to test. A good core web vitals explanation names the trade-off, the failure mode, and the evidence you would use before choosing. Use core web vitals once more at the decision point so the answer reads as judgement rather than a detached example.
Keep core web vitals explicit in the answer because that is the concept the interviewer is actually trying to test. A good core web vitals explanation names the trade-off, the failure mode, and the evidence you would use before choosing.
Why the lab run is clean and the users are not
Start by explaining the discrepancy rather than doubting the reports, because the reasons are specific and they tell you where to look. A layout shift happens when an element that has already been painted moves to a different position without the user having asked for it, and whether that happens depends almost entirely on the order and timing in which things arrive. A synthetic audit runs on a fast connection from a data centre, so an image, a font and a hero payload all land within a few milliseconds of each other and there is no interval during which the page is laid out one way and then another.
Then there is everything the lab run does not have. It usually has no consent banner, because that depends on the visitor's region and on whether a choice has already been stored. It frequently has no advertising or third-party embeds, because those are stripped from test builds or blocked by the harness. It is not signed in, so personalised modules, saved-address blocks and notification bars are absent. It has one viewport and one device pixel ratio, and a shift that only appears when a heading wraps to two lines is a function of viewport width. And it does not scroll, so anything that renders as it comes into view is never exercised at all.
None of that means synthetic testing is useless. It means layout shift in particular is a field metric, and the first move is to look at field data rather than to tune the lab.
Getting attribution out of real sessions
The browser exposes each individual shift, and crucially it names the nodes that moved, which turns a score into a list of culprits.
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Shifts that follow a user interaction are flagged and are not counted
// as unexpected, so exclude them before you go hunting.
if (entry.hadRecentInput) continue;
for (const source of entry.sources) {
report({ value: entry.value, node: describe(source.node),
from: source.previousRect, to: source.currentRect });
}
}
}).observe({ type: 'layout-shift', buffered: true });
Send a compact description of the largest shift per session, along with the viewport size, the connection type if available and the route, and the pattern usually resolves within a day. What you are looking for is not the average but the segmentation: shifts concentrated on one route, on narrow viewports, on first-time visitors, or on sessions from a region where the consent banner appears. That segmentation is the whole diagnosis, because each of those has a different fix.
Two properties of the score are worth knowing while reading the data. It is accumulated across the whole life of the page rather than measured during load, so a shift caused by content injected after five minutes of reading counts. And it is grouped into windows of consecutive shifts rather than summed indiscriminately, which is why a single burst of movement during load is scored as one event rather than as twenty. In Chrome's developer tools you can also record a throttled load and read the shifts back with their culprits, which is the right way to confirm a fix once field data has told you where to look.
Reserve the space, do not guess at it
Almost every fix is the same idea: make the box the right size before the content arrives. The mechanism differs by content type.
For images and video, provide the intrinsic dimensions. The width and height attributes are not legacy decoration; browsers derive an aspect ratio from them and reserve a correctly proportioned box even in a responsive layout where the rendered width comes from CSS. Where the markup cannot carry them, aspect-ratio in CSS does the same job. An image with neither collapses to nothing and then pushes everything below it down when the bytes arrive, which is the single most common source of this complaint.
For content that renders on the client after a fetch, the skeleton has to match the final dimensions rather than merely existing. A three-line placeholder replaced by a five-line result shifts the page, and a spinner in a small box replaced by a full table shifts it violently. Where the final size genuinely varies, decide the box from the layout rather than from the content: fix a height and scroll inside it, or accept a defined maximum and clamp.
For lazily rendered sections, content-visibility: auto is a real performance win and a real shift generator, because the browser needs an estimate of the skipped content's size. Supplying contain-intrinsic-size gives it that estimate, and the shift when a section is finally rendered is proportional to how wrong the estimate was.
Two small ones with outsized effect: scrollbar-gutter: stable removes the reflow that happens when a page grows tall enough to acquire a scrollbar, and any hover or focus style that adds a border should adjust an existing transparent border or use outline, since adding thickness to a box on hover moves its neighbours.
Fonts move text without moving any boxes
A web font that arrives after first paint replaces text already rendered in a fallback face, and because the two faces have different metrics the replaced text occupies a different number of lines or a different width. Nothing about your layout is wrong; the content changed size.
There are three levers and they compose. Preload the font files that are used above the fold so they arrive closer to first paint. Choose a fallback face whose metrics are close to the web font, which is easier for a body face than for a display one. And declare metric overrides on the @font-face rule for the fallback, using size-adjust and the ascent, descent and line-gap override descriptors, so the fallback is rendered at metrics matching the real font and the swap changes glyph shapes without changing line boxes. The last of those is the one candidates rarely know, and it is the only one that reduces the shift to nothing rather than merely shrinking the window in which it can happen.
The one you cannot fix with CSS
Content injected above existing content is a structural problem, not a styling one. A consent banner, a promotional bar or an error strip prepended to the document pushes the entire page down after the user has started reading. Reserving space for it is dishonest, because for returning visitors who have already consented the space stays empty.
The options that do not shift anything are to position it out of flow as a fixed overlay, or to have it present in the initial server-rendered markup for the visitors who will see it, decided at the edge from the same signal that decides whether to show it at all. Ad slots are the same shape of problem with a narrower fix: a slot whose creative can be one of several heights should reserve the height you actually sell most often and be positioned so that a mismatch shifts nothing above it.
Layout shift is decided by arrival order, so it lives in field data and not in your audit. Get the shifted nodes out of real sessions, then make every box the right size before its content lands, including the boxes that text occupies.
© 2026 Preptima. Originally published at preptima.com.
Likely follow-ups
- A consent banner is injected at the top of the document. What are your options, and which one does not shift anything?
- An ad slot can render at one of three heights. How do you reserve space for that honestly?
- The shift only happens when the user scrolls into a section that renders lazily. Which declaration is missing?
- How would you stop this regressing without blocking every pull request on a synthetic audit?
Related questions
- A page has a poor Largest Contentful Paint. How do you diagnose it and what do you actually change?hardAlso on core-web-vitals and images7 min
- INP went from 150ms to 400ms the week a marketing tag was added. Marketing says the script is async and cannot be the cause. How do you prove it either way and fix it?hardAlso on core-web-vitals4 min
- Field data says your page has an INP of 400 ms. How do you find the cause and fix it?hardAlso on core-web-vitals4 min
- A transform has been writing wrong revenue figures for three days and six downstream tables have consumed it. How do you backfill the corrected data without double-counting anything?hardSame kind of round: scenario4 min