Frontend Performance Interview Questions

Performance interviews reward measurement over folklore: which metric moved, what caused it, and how you would stop the win eroding next quarter.

This topic has 26 curated Frontend Performance questions covering Core Web Vitals, bundling and code-splitting, rendering cost, images and layout shift. Below are 8 of them in full, with the same answers you get in the interactive tool.

Practise all 26 Frontend Performance questions →

Difficulty breakdown

  • Easy4
  • Medium13
  • Hard7
  • Expert2

Question formats

  • Multiple choice6
  • Concept5
  • Scenario5
  • Code review2
  • Debugging2
  • Follow-up2

What these questions cover

  • bundling
  • bundle-size
  • cls
  • images
  • lcp
  • loading
  • measurement
  • prefetch
  • process
  • rendering

8 example Frontend Performance interview questions

ExpertArchitecture

Marketing wants five more third-party tags. How do you govern that without simply saying no?

Make the cost visible and the decision shared rather than adjudicating each request. Establish a third-party budget in the same terms as your own — total bytes, main-thread time, and impact on INP and LCP — and measure each proposed tag against it before approval, using a real before/after profile rather than the vendor’s claim. Require every tag to load non-blocking, to be attributable to an owner with a review date, and to be removable — tags that nobody can explain accumulate forever. Where possible move them server-side so they cost nothing on the client. And make the trade explicit to the requester: adding this tag costs X ms of interaction latency, which historically maps to Y conversion — that reframes it as a business decision rather than engineering obstruction.

Expert-level because it is a governance answer with measurement behind it, not a technical veto.

MediumBest practice

A library would save you two days of work and adds 40KB gzipped. How do you decide?

Ask what it costs on the critical path, not in the abstract: does it land in the initial bundle or behind a lazy route; does it tree-shake, or is it one CommonJS blob; does it drag in a date or locale bundle several times its own size. Then weigh that against what you would maintain instead — 40KB for a battle-tested date or virtualisation library is usually a bargain, and 40KB to format three strings is not.

The failure in both directions is deciding by reflex. Blanket "no dependencies" gets you a worse in-house version of the same code with none of the edge cases handled; blanket "just install it" is how a bundle grows 40KB at a time with no single decision to blame. Checking the real cost is quick — a bundle analyser or bundlephobia gives the number, and the transitive dependencies are usually where the surprise lives.

MediumWrite code

Instrument a specific interaction so you can prove an optimisation worked

Measuring to the next paint rather than to the end of the handler is the detail that makes the number match what the user experiences — and it is what makes an optimisation provable rather than asserted.

MediumCode review

Review this gallery for performance

{images.map((src) => (
  <img key={src} src={src} loading="lazy" className="w-full" />
))}

Three problems. (1) `loading="lazy"` is applied to every image including the first, which is almost certainly the LCP element — lazy-loading it delays the metric it defines. The first image or two should be eager, ideally with `fetchpriority="high"`. (2) No `width`/`height` or `aspect-ratio`, so nothing reserves space and every image that loads shifts the layout, hurting CLS. (3) A single `src` with no `srcset`/`sizes` ships one resolution to every device, so phones download desktop-sized images. Also worth flagging: no `alt` text at all, which is an accessibility failure as well as a quality one.

The lazy-loaded LCP image is the counter-intuitive one — a performance attribute applied uniformly becomes a performance regression.

EasyConcept

Give an example where a page gets objectively slower but feels faster, and explain why

Streaming a page in stages: total load time increases slightly because of the extra overhead, but the user sees meaningful content far sooner, so it feels faster. Other examples: an optimistic update makes an action feel instant while the request is still in flight; a skeleton that matches the final layout makes the same wait feel shorter than a spinner because progress is legible; and prefetching on hover spends bandwidth to make the click feel immediate. The underlying reason is that people experience RESPONSIVENESS and progress, not elapsed milliseconds — which is why Core Web Vitals measure time to meaningful paint and interaction latency rather than total load time.

The general principle is that users experience progress, not totals — so making meaningful content appear sooner beats reducing total load time. That is also why a skeleton matching the final layout beats a spinner: it makes the wait legible and avoids the layout shift when content replaces it.

HardDebugging

A click takes 400ms to show any response, but the handler itself profiles at 5ms. Where is the time?

button.addEventListener('click', () => { setState(next); });  // handler: 5ms

The handler is not the cost — the work it triggers is. INP measures from the interaction to the NEXT PAINT, so it includes input delay (the main thread was already busy when the click arrived), processing, and the render and paint that follow. Profile the whole interaction rather than the handler: look for a long task already in progress when the click landed, a large synchronous re-render caused by the state change, or a forced synchronous layout in a child. Fixes target whichever dominates: break up or defer the pre-existing long task, reduce the render cost, mark the update as a transition so the visual response is not queued behind it.

The distinction between handler time and interaction latency is the whole point — optimising the handler would have achieved nothing here.

MediumFollow-up

You split the bundle by route and the first interaction got slower. Why?

You moved work from a single up-front download to an on-demand one, so the user now waits at the moment they click. The initial bundle is smaller and first paint improved, but the route chunk is fetched, parsed and executed only once the navigation begins — and if that chunk then fetches its own data, you have two round trips in series where you previously had none.

Splitting is still right; the missing piece is prefetching. Fetch the likely next chunk during idle time or on hover, so the code is in cache before the click. It is also worth checking the split points: splitting too finely produces many small requests and shared code duplicated across chunks, which is slower than a few well-chosen boundaries. This is the general shape of the trade — code splitting does not remove work, it relocates it, and the win comes from relocating it somewhere the user is not waiting.

HardMultiple choice

What does Interaction to Next Paint capture, and which interaction does it report?

INP observes three interaction types only — clicking, tapping and pressing a key. Scrolling, hovering and zooming are excluded. It measures from the interaction to the next paint, covering input delay, processing and presentation. For a visit with fewer than 50 interactions the reported value is the WORST one; above that, one highest interaction is discarded for every 50, so a single freak outlier cannot define the score. Field tools then report the 75th percentile across page views. Good is 200ms or less; above 500ms is poor.

The remaining 18 Frontend Performance questions — plus mock interviews, spaced revision and progress tracking — are in the free interview prep workspace.

Related interview topics