This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.
Identity, Image, and Privacy Notice
To respect individual privacy, some names, profile photographs, avatars, biographical details, and other identifying information displayed in this guide may be replaced with pseudonyms, licensed stock images, illustrative avatars, composite images, or representative descriptions. Unless a person is expressly identified as an actual contributor, a displayed name, image, or profile should not be understood as depicting or identifying a specific candidate, interviewer, employee, or other real individual. These representations are provided for editorial and illustrative purposes only and do not imply endorsement, employment, participation, or affiliation with this guide or any company mentioned in it. Any resemblance to an actual person is coincidental.
Company Notice
This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.
Questions or comments?
Contact us for general questions, or share feedback, technical corrections, and comments with the community.
81. What causes Cumulative Layout Shift on a web page?PerformanceEasy
i Question Details
Consider a news page where images, a late-loading advertisement, and a web font can move already-visible content. Explain which unexpected shifts contribute to CLS, which user-initiated movements do not, and what browser tooling or layout-shift entries you would use to identify the affected elements. Do not treat animation smoothness as the same metric.
Short Interview Answer (30-60 seconds)
I would measure unexpected movement of visible content with CLS and inspect the layout shift entries that caused it. Common causes are images without reserved space, advertisements inserted after content is visible, and web font changes that alter text size or wrapping. A shift with recent qualifying user input is normally excluded from CLS. I would use the browser Performance panel or PerformanceObserver to inspect the shift score and affected elements. For each shift, the score uses impact fraction and distance fraction, while CLS uses the largest session window total. I would then reserve stable space or correct the measured source and retest the same page.
Detailed Explanation
CLS tells us whether content that a person can already see suddenly moves when they did not expect it. On a news page, an image may appear and push text down, an advertisement may be inserted above an article, or a new font may change the size and wrapping of a headline. These movements can make people lose their reading position or click the wrong thing. Movement that follows recent qualifying user input is normally excluded. Animation smoothness is a separate concern, so it should not be treated as the same measurement.
Useful Questions to Ask the Interviewer
Should I focus on CLS during the first page load or during the full page lifetime?
Should I explain how to identify the exact elements that moved?
Do you want both browser DevTools and PerformanceObserver explained?
How to Explain It in an Interview
I would start with the user visible symptom. Content that was already visible changes position unexpectedly. The metric is Cumulative Layout Shift, or CLS, which measures visual instability in the browser.
For one layout shift, the browser calculates a score using impact fraction and distance fraction. Impact fraction represents how much of the viewport is affected by unstable content. Distance fraction represents how far that content moved relative to the viewport. The shift score is the product of those two values.
CLS is not simply the sum of every layout shift during the full page lifetime. The browser groups qualifying shifts into session windows. A session window ends after a gap of at least one second without a shift or after five seconds from the first shift in that window. The reported CLS value is the largest total from any session window.
On the news page in this example, an image without reserved dimensions can load after text is visible and push that text downward. A late loading advertisement can be inserted above existing article content and move it. A web font can replace the fallback font with different character measurements, which can change line wrapping and move nearby content. These are examples of unexpected layout movement that can contribute to CLS.
Movement related to recent qualifying user input is handled differently. A LayoutShift entry exposes hadRecentInput. When that value is true, the shift is excluded from CLS. For example, an expected layout change that happens shortly after a supported click or key action should not be treated like an unexpected page shift. Scrolling itself does not create a layout shift just because content moves through the viewport.
Animation smoothness is also a different performance concern. A transform based animation can move pixels without changing layout, so that movement does not create a layout shift. However, an animation that changes layout properties can still create layout shifts. The important distinction is whether layout unexpectedly changes, not whether the animation looks smooth.
For evidence, I would record the same news page in the browser Performance panel and inspect layout shift events or layout shift regions. This shows when shifts happened and helps connect them to visible elements. I can also use PerformanceObserver to collect layout shift entries in JavaScript.
A LayoutShift entry can provide value, hadRecentInput, and sources. Each source can identify an affected node and include previousRect and currentRect, which show where that element was before and after the shift. The source identifies an element that moved, but it is not always the original cause. For example, article text may be listed as the shifted element even though a late advertisement above it caused the movement.
I would use the evidence before choosing a fix. Images should reserve their layout space with dimensions or an aspect ratio before the image finishes loading. Advertisement containers should reserve a predictable amount of space before the advertisement appears. For fonts, I would reduce metric differences between the fallback and final fonts and choose an appropriate font display strategy. Using font display swap by itself does not guarantee a lower CLS if the replacement font has different measurements.
I would then repeat the same scenario with the same browser, viewport, network condition, build, cache state, and measurement window. I would compare the CLS value and the relevant layout shift entries. I would also verify that images, advertisements, text, keyboard behavior, and screen reader behavior still work correctly.
The main tradeoff is that reserved advertisement or image space may temporarily appear empty. Font choices can also affect design and loading behavior. The goal is not to remove useful content. The goal is to keep the layout predictable while that content loads.
Technical Approach
Define the symptom as unexpected movement of content that is already visible.
Use CLS as the main visual stability metric.
Reproduce the news page with the same browser, viewport, network condition, build, cache state, and measurement window.
Record the page with the browser Performance panel and inspect layout shift events or layout shift regions.
Use PerformanceObserver when runtime layout shift entries are useful.
Inspect each entry value, hadRecentInput, and available sources such as the affected node, previousRect, and currentRect.
Connect the shifted element to the actual cause, such as an image without reserved space, a late advertisement, or a font metric change.
Exclude shifts with recent qualifying user input from the CLS calculation.
Apply one change that addresses the measured cause, such as reserving layout space or reducing font metric differences.
Repeat the same scenario and verify the CLS value, layout shift entries, correctness, and accessibility.
Practical Insights
This investigation does not depend on an important algorithmic time complexity. The main costs come from measurement and browser layout work. Recording a Performance trace adds temporary profiling overhead, so it is mainly a diagnostic tool. PerformanceObserver can collect runtime layout shift entries with lower overhead, but production telemetry still needs careful sampling and storage. Reserving space may leave a temporary empty area while an image or advertisement loads. Font changes may require design work to keep fallback and final text measurements similar. These costs are usually small compared with the benefit of a stable page.
Why Interviewers Ask This
Interviewers ask this question to see whether I understand visual stability in the browser. They want to know whether I can identify unexpected layout movement, separate it from expected movement after user input, and use browser evidence to find the elements involved. They are also checking whether I understand that CLS is different from animation smoothness and whether I can choose fixes that address the measured cause instead of guessing.
Common interview mistakes
Common mistakes include assuming every visible movement contributes to CLS, treating animation smoothness as the same metric, and blaming a late resource without inspecting actual layout shift evidence. Another mistake is forgetting that a shift with recent qualifying user input is excluded from CLS. Developers may also look only at the final CLS number without checking which elements moved. A source element in a layout shift entry may be the element that moved rather than the element that caused the movement. It is also incorrect to say that every intentional animation is automatically excluded. A layout changing animation can still create shifts. Other mistakes include changing several things before measuring, comparing different page conditions before and after a fix, and using one local run as proof for all real users.
Interview tip
Explain CLS as a visual stability problem first. Use the same news page example from start to finish. Name the three common causes, explain which recent user input shifts are excluded, describe impact fraction and distance fraction briefly, and show how the Performance panel or layout shift entries identify moved elements. Finish by explaining how you would reserve stable space or correct font metrics and then retest the same page.
Interviewer may ask next
What if the page moves after a user clicks a control?
I would inspect the LayoutShift entry before deciding whether it contributes to CLS. For this news page, I would check hadRecentInput for the shift and inspect the affected elements and timing. If hadRecentInput is true, that shift is excluded from CLS because it follows recent qualifying user input. This matters because I should not spend time optimizing an expected interaction while leaving unexpected shifts from the image, advertisement, or font unchanged.
What tradeoff can appear when you reserve space for a late advertisement?
The main tradeoff is that the news page may temporarily show empty space while the advertisement is loading, unavailable, or smaller than the reserved area. I would still reserve a predictable container when the alternative is moving visible article content after it appears. I would validate the change with the same browser, viewport, network condition, build, cache state, and measurement window. I would compare CLS and the relevant layout shift entries and also check responsive layouts so the reserved area does not create an unacceptable permanent gap.
82. How do lab performance measurements differ from field measurements?PerformanceEasy
i Question Details
Compare a repeatable local or synthetic test with real-user monitoring for the same page. Cover the environment represented, variability, available diagnostics, population bias, and the kinds of regressions each method can reveal. Explain why a fast laboratory run does not prove that users on slower devices and networks receive the same experience.
Short Interview Answer (30-60 seconds)
I use lab measurements to test the same page in a controlled and repeatable environment, and I use field measurements to understand what real users experience. Lab tests use known device and network conditions, so results are more stable and detailed diagnostics are easier to collect with browser DevTools or Lighthouse. Field measurements come from real devices, networks, browsers, and user behavior, so they have much more variation. Lab tests are useful for finding code and asset regressions. Field data can reveal production problems that appear only for certain users. A fast lab result does not prove that users on slower devices or networks get the same experience, so I use both.
Detailed Explanation
A laboratory test checks the same page in a setup that I can control and repeat. This makes changes easier to compare. A field measurement watches what happens when real people use the page on their own devices and networks. Those conditions can be much faster or slower than my test setup. The two methods therefore answer different questions. One gives detailed evidence in a known environment. The other shows what real users actually experience. A fast laboratory result cannot prove that every user gets the same result because real devices, networks, browsers, and behavior vary.
Useful Questions to Ask the Interviewer
Are we comparing the same page and user action in both environments?
Which device classes, browsers, network conditions, and user groups matter most in production?
Do we already collect field performance data for this page?
How to Explain It in an Interview
I would compare the same page in both lab and field measurements.
In the lab, I define a known setup. That includes the browser, device profile, network condition, build, cache state, and measurement window when they matter. I run the same navigation or interaction several times. Because the environment is controlled, the results have lower variation and are easier to reproduce. This makes lab testing useful for comparing builds and finding regressions.
For a page load, I can follow the browser path from navigation to resource discovery, download, JavaScript parsing and execution, rendering, and interactivity. Browser DevTools can show detailed timing and execution evidence. Lighthouse can also provide controlled diagnostic guidance. This deep diagnostic detail is one of the main strengths of laboratory testing.
Field measurement looks at the same page while real users load and interact with it. Their devices, browsers, networks, locations, background activity, and behavior differ. The results therefore have much more variation. That variation is valuable because it can reveal conditions that one controlled setup does not represent.
Population is another difference. A lab run represents the test environment that I selected. It does not represent every user. Field data represents the users who are actually measured, but it can still have population bias. Sampling, instrumentation, consent rules, or missing user groups can affect what the data represents.
The two methods can also expose different regressions. Lab testing is good for finding code, asset, loading, JavaScript, or rendering changes under known conditions. Field data can reveal production problems that appear only on slower devices, weak networks, certain browsers, certain regions, or unusual usage patterns.
The most important conclusion is that lab speed and real user speed are not the same thing. A modern device on a fast controlled network can perform very well while a slower device on a poor network performs badly on the same page. I use lab measurements for repeatable investigation and detailed diagnosis, then field measurements to validate what real users actually experience.
Technical Approach
Choose one page and one navigation or interaction to compare.
Define the laboratory environment, including browser, device profile, network condition, build, cache state, and measurement window when relevant.
Run the same scenario several times so normal variation is visible instead of trusting one result.
Use browser DevTools and controlled diagnostic tools to inspect loading, JavaScript execution, rendering, and interactivity when those areas are relevant.
Collect field measurements for the same page from real users.
Segment field results by useful dimensions such as device class, browser, network quality, or region when the telemetry supports it.
Compare the laboratory evidence with the field evidence. Look for regressions that reproduce in both places and problems that appear only in real conditions.
After a change, repeat the same laboratory scenario and continue checking field data so the real user result is not assumed from the laboratory result alone.
Practical Insights
There is no meaningful algorithmic complexity to calculate for this comparison. The main cost is measurement work. Laboratory testing needs a controlled setup and repeated runs, but it is easier to reproduce and gives detailed evidence. Field monitoring needs browser instrumentation, data collection, sampling, segmentation, and analysis across many users. Field data is harder to reproduce because real conditions vary. Using both costs more than using only one method, but each covers an important limitation of the other.
Why Interviewers Ask This
Interviewers ask this to see whether I understand that controlled lab measurements and field measurements answer different questions. They want to know whether I can reproduce the same page in a known environment, use detailed browser diagnostics, understand actual user experience across many devices and networks, recognize population bias, and avoid treating one fast laboratory result as proof of good production performance.
Common interview mistakes
Common mistakes include treating one Lighthouse run as proof of production performance, comparing different pages or conditions between measurements, testing only on a fast modern device and network, ignoring slower devices and poor networks, assuming field data has no population bias, using only averages when a distribution is available, expecting field telemetry to provide the same deep diagnostics as browser DevTools, and changing code before measuring where the delay actually occurs. Another mistake is declaring success after the laboratory result improves without checking whether real users also receive a better experience.
Interview tip
Start with the main difference: lab data gives a controlled and repeatable view, while field data gives a variable real user view. Then compare environment, variability, diagnostics, population, and the kinds of regressions each method can reveal. Finish by explaining that a fast lab result describes only the chosen setup, so field data is still needed to validate actual user experience.
Interviewer may ask next
What would you do if the laboratory test is consistently fast but field measurements show that some users still have a slow experience?
I would treat that as evidence that the controlled setup does not represent all real user conditions. For the same page, the laboratory boundary covers my chosen browser, device profile, network condition, build, and cache state. The field boundary covers real devices, browsers, networks, locations, background activity, and user behavior. I would segment the field data to find where the slow experience is concentrated, then reproduce those conditions in the laboratory when possible. This matters because a fast controlled run does not prove that slower devices or networks behave the same way.
Why not use only field measurements if they represent real users?
I would not use field measurements alone because they show real user impact but usually provide less controlled and less detailed diagnostic evidence. For the same page, field data can show which users or environments are slow, while a repeatable laboratory scenario can help isolate whether the delay comes from loading, JavaScript execution, rendering, or interactivity. The tradeoff is that the laboratory represents only the environment I configured, while field data covers a broader population but has more variation and may contain sampling or instrumentation bias. I use both because they answer complementary questions.
83. How would you diagnose a slow LCP hero image from a network waterfall?PerformanceMedium
i Question Details
A product page has a p75 LCP of 4.1 seconds on mobile field data. The LCP element is a 900 KB hero image inserted by a stylesheet-backed component after the main JavaScript bundle executes. In a cold-load trace, HTML arrives at 600 ms, CSS at 1.1 s, the 420 KB script finishes at 2.0 s, and the hero request starts at 2.1 s. Describe the evidence you would collect, separate resource discovery, download, server, and render delay, and propose an ordered experiment plan with before-and-after measurements. Preserve the responsive image behavior and do not assume that simply compressing the file addresses the full delay.
Short Interview Answer (30-60 seconds)
I would start by measuring where the 4.1 second LCP is spent. The strongest clue is that HTML arrives at 600 ms, CSS at 1.1 seconds, JavaScript finishes at 2.0 seconds, and the 900 KB hero request does not start until 2.1 seconds. That shows a large discovery delay before the image even begins loading. I would use the network waterfall, Resource Timing, LCP observation, and a Performance trace when needed to separate discovery, TTFB, download, and render delay. I would test earlier responsive discovery first, then server wait, transfer size, and render work, and compare the same mobile cold load before and after each change.
Detailed Explanation
The page feels slow because its large main picture appears much later than the rest of the page. Real mobile data shows that this picture is visible at about 4.1 seconds for the slower group of users. The page itself starts arriving much earlier, but the picture is not requested until after the main script has finished. So a large part of the wait happens before the picture download even starts. I would measure each part of that wait, test one change at a time, and repeat the same mobile test after every change.
Useful Questions to Ask the Interviewer
Should the mobile field p75 LCP remain the main success metric?
Can I reproduce the product page with a cold cache and a representative mobile network profile?
Must the current responsive image selection and layout behavior stay unchanged?
Is the hero always the LCP element on this route for the mobile viewport being measured?
How to Explain It in an Interview
I would begin with the user visible symptom. Mobile field data shows a p75 LCP of 4.1 seconds, and the LCP element is the 900 KB hero image. I would use field data to understand real users, then reproduce the same product page with a cold cache, the production build, a representative mobile device class, and a representative mobile network profile. The controlled trace is for diagnosis. The field p75 is the production result that matters.
The waterfall gives the first strong clue. HTML arrives at 600 ms. CSS arrives at 1.1 seconds. The 420 KB main JavaScript bundle finishes at 2.0 seconds. The hero request starts at 2.1 seconds. The stylesheet backed component inserts the image only after JavaScript finishes, so the browser discovers the critical image very late. From HTML arrival at 600 ms to the hero request at 2.1 seconds, the diagram shows about 1.5 seconds of discovery delay. This is the first bottleneck to test.
I would separate the LCP path into four measured parts. First is resource discovery delay, which is the time before the browser starts the hero request. Second is server wait, measured as TTFB for that image request. I treat that as an external timing boundary. Third is download time, measured from the resource timing entry and transfer size. Fourth is render delay, measured from response end until the image becomes the LCP element, including decode and the browser work needed for layout, paint, and compositing.
For evidence, I would collect the DevTools network waterfall, Resource Timing for the hero request, LCP entries through PerformanceObserver where supported, field p75 data, and a Performance trace if I need to inspect decode work or long main thread tasks around layout and paint. I would confirm that the same hero image is the LCP element and that its request really begins only after the component appears.
My first experiment would remove the discovery delay. I would preload the responsive hero in the document head before CSS or JavaScript can block discovery, using a preload that matches the image source set and sizes rules. Another valid change is to expose the hero URL in markup early enough for the browser to discover it without waiting for the main bundle. I would keep the responsive image behavior so each viewport still receives the correct candidate.
After that, I would measure TTFB for the hero as its own external wait. If that part is meaningful, I would test external delivery changes such as CDN cache behavior, image routing, connection reuse, TLS setup, and HTTP 2 or HTTP 3. I would measure TTFB again rather than assume those changes help. For the image bytes themselves, I would focus on correct dimensions, responsive candidates, and an efficient format such as AVIF or WebP. A smaller file can reduce transfer time, but it cannot remove the original discovery delay.
Finally, I would inspect render delay. If the bytes arrive but the hero still becomes visible late, I would look for image decode cost or long main thread work that delays layout, paint, or compositing. I would test deferring noncritical JavaScript, splitting code, or reducing proven long tasks only when the trace shows that work is delaying the hero.
For every experiment, I would repeat the same page, device class, network profile, production build, and cold cache state. I would compare hero request start time, TTFB, transfer size, download duration, render delay, and LCP before and after. After rollout, I would confirm field p75 moves toward the below 2.5 second goal shown in the diagram, responsive image selection still works, there is no layout shift, and CLS and INP do not regress. I would also verify the image still has the correct accessible text and semantics, and check whether the bottleneck moved to another part of the loading path.
Technical Approach
Define the baseline with mobile field p75 LCP at 4.1 seconds.
Reproduce the same product page with a cold cache, production build, representative mobile device class, and representative mobile network profile.
Confirm that the 900 KB hero image is the LCP element.
Read the waterfall and record HTML at 600 ms, CSS at 1.1 seconds, JavaScript completion at 2.0 seconds, and hero request start at 2.1 seconds.
Separate the path into resource discovery delay, image TTFB, download time, and render delay.
Use Network tools and Resource Timing for request timing. Use PerformanceObserver for LCP and a Performance trace when render work needs investigation.
Test earlier responsive discovery first because the request currently waits for JavaScript completion.
Retest the same scenario and compare request start time and LCP.
If TTFB is meaningful, test delivery and caching changes and measure TTFB again.
Optimize the responsive image transfer with correct dimensions and efficient formats, then measure transfer size and download time.
If render delay remains, trace decode and main thread work and reduce only the work proven to delay visibility.
Validate field p75 after rollout and check responsive behavior, CLS, INP, visual correctness, and whether the bottleneck moved elsewhere.
Practical Insights
The main cost is measurement and maintenance work rather than algorithm complexity. Adding a responsive preload or changing when the hero becomes discoverable adds markup and maintenance responsibility. The preload must match the responsive source set and sizes rules or the browser can fetch an unnecessary image. Image format and dimension changes affect build work, caching, and asset management. Performance tracing also takes investigation time. The safest approach is to change one measured bottleneck at a time, repeat the same mobile cold load, and confirm that the delay did not move into rendering or another resource.
Why Interviewers Ask This
Interviewers ask this to see whether the candidate can read a browser waterfall, separate resource discovery from server wait, download time, and render delay, and then choose changes that match the measured delay. They also want to see disciplined before and after testing, correct use of field data and browser tools, protection of responsive image behavior, and awareness that making the file smaller does not remove a late discovery problem.
Common interview mistakes
Common mistakes are compressing the 900 KB image before proving where the time is lost, treating the full image request as one number instead of separating discovery, TTFB, transfer, and render delay, using one local run as production proof, comparing different cache or network conditions before and after, adding a preload that does not match the responsive source set and sizes rules, assuming server wait is the only network problem, ignoring main thread work after the bytes arrive, and declaring success without checking field p75, responsive behavior, CLS, INP, visual correctness, and whether the bottleneck moved elsewhere.
Interview tip
Lead with the measured clue: the hero request starts at 2.1 seconds only after the main JavaScript bundle finishes. Then walk through discovery, TTFB, download, and render delay in that order. Tie each experiment to one measured part, and finish by saying that you would repeat the same mobile cold load and confirm the result in field p75 data.
Interviewer may ask next
What if the hero file becomes much smaller but p75 LCP barely changes?
I would measure the same product page and mobile cold load again instead of assuming the image optimization failed. If the hero still starts near 2.1 seconds, the discovery delay is still present, so the smaller file only reduces transfer time. If the request starts earlier but LCP still finishes late, I would inspect TTFB and render delay next. This matters because reducing one part can expose another part as the new bottleneck.
What tradeoff would you watch when preloading the responsive hero image?
The main tradeoff is fetching the wrong responsive candidate or fetching a resource that is not actually critical. For this 900 KB hero, the preload must match the same source set and sizes rules used by the page. I would compare the same mobile cold load before and after, confirm that the request starts earlier, and verify that transfer size does not increase because of duplicate or incorrect downloads. I would then watch field p75 LCP, CLS, and INP after rollout.
84. How would you investigate poor INP in a searchable product list?PerformanceMedium
i Question Details
Field data shows p75 INP of 360 ms when users type into a filter box containing 8,000 client-side records. A trace shows a keydown task that filters, sorts, rebuilds result markup, and recalculates layout before the next paint. Explain how you would break down input delay, processing time, and presentation delay; identify the dominant work with browser tooling; and design experiments involving scheduling, reduced work, and rendering scope. State how you would preserve keyboard behavior and verify that results stay correct.
Short Interview Answer (30-60 seconds)
I would start with the p75 INP of 360 ms and reproduce the same typing interaction with 8,000 client side records in the browser Performance panel. I would split the interaction into input delay, processing time, and presentation delay, then inspect the keydown task, Bottom Up view, Call Tree, and rendering work to see whether filtering, sorting, markup rebuilding, or layout dominates. I would test one change at a time, such as doing less work, rendering fewer rows, scheduling nonurgent work, or moving suitable filter and sort work to a Web Worker. I would keep the input responsive, preserve keyboard behavior and focus, compare the same workload before and after, and verify result count, order, edge cases, and field INP.
Detailed Explanation
Users feel a delay while typing into a product search box. The page holds 8,000 items, and each key press causes several large pieces of work before the updated list appears. The current real user result is slow, with p75 INP at 360 ms. I would first find which part of the interaction consumes the most time. Then I would test smaller, safer changes one by one. The goal is to make typing feel immediate while keeping the same results, order, focus, and keyboard controls.
Useful Questions to Ask the Interviewer
Which browsers and device classes show the worst INP for this filter interaction?
Is the current 360 ms p75 measured for the same product list size and typing flow that we can reproduce in the lab?
Must every keystroke immediately update all matching rows, or can nonurgent result work be scheduled while the input stays responsive?
Which keyboard behaviors and accessibility rules must remain unchanged?
How to Explain It in an Interview
I would begin with the user visible symptom: typing into the filter has p75 INP of 360 ms for a list of 8,000 client side records. For this interaction, INP can be understood as input delay plus processing time plus presentation delay. Input delay is the wait before the event handler starts. Processing time is the JavaScript and related work caused by the interaction. Presentation delay is the time from that work finishing until the next visible paint.
I would reproduce the same typing flow with a production build on a representative browser and device class. I would record the interaction in the browser Performance panel. I would find the keydown event and inspect the main thread around it. The trace already tells us that filtering, sorting, rebuilding result markup, and recalculating layout all happen before the next paint, so I would measure how much each part contributes instead of guessing.
I would use Bottom Up and Call Tree views to quantify the expensive JavaScript functions. I would also inspect style calculation, layout, paint, and invalidated rendering areas. The diagram shows JavaScript as the dominant category with layout and paint also contributing. I would treat the percentages and per function durations shown in the diagram as illustrative trace values, not as production facts unless the actual recording reports them. PerformanceObserver can collect supported interaction metrics in runtime telemetry. Lighthouse can provide controlled diagnostic guidance, but I would not use one Lighthouse run as proof of production performance. Field telemetry is the source for real user distributions, while the lab trace is the source for detailed causality.
Then I would run controlled experiments, one change at a time. First, I would reduce work per keystroke. I could precompute normalized searchable fields, use a Set or Map for repeated lookups, filter before sorting, avoid a full sort when it is not needed, and cache proven repeated work. Second, I would reduce rendering scope by windowing or virtualizing the result list, limiting the rendered rows, batching DOM writes, reading layout before writing, and using content visibility where it fits the component.
Scheduling can help when work is truly deferrable. Debouncing can reduce repeated searches during rapid typing, but it adds a short delay before results update, so I would test the user experience rather than assume it is better. requestIdleCallback can handle nonurgent work, but idle time is not guaranteed and a fallback may be needed. requestAnimationFrame can coordinate visual updates, but it does not make expensive computation free. If the filter and sort remain CPU heavy after reducing work, I would test a Web Worker for that computation. The worker can return minimal result data while the main thread keeps DOM updates and rendering. I would include worker startup, message transfer, extra memory, cleanup, and maintenance in the tradeoff.
I would preserve keyboard behavior throughout the experiments. The input should keep focus and remain responsive. Enter, Escape, arrow keys, Tab, Home, and End should keep their expected behavior when those controls are part of the component. Result order must remain stable, and screen reader announcements should remain correct. I would verify result count, top result order, special characters, an empty query, fast typing, paste input, and focus behavior.
Finally, I would repeat the same workload before and after each change. I would compare p75 INP, the interaction distribution, long task count, dropped frames, and the trace itself. The diagram uses a target below 200 ms, so I would use that as the desired project target, not as a guaranteed outcome. I would also check that the bottleneck did not move from JavaScript into layout, paint, worker communication, or memory. For material changes, I would use a feature flag when risk justifies it, monitor field INP and errors after release, and roll back if responsiveness or correctness regresses.
Technical Approach
Start with the field symptom. Record that p75 INP is 360 ms for typing into the filter over 8,000 client side records.
Reproduce the same interaction in a controlled browser session using a representative device class, production build, and consistent cache state.
Record the interaction in the browser Performance panel. Find the keydown event and the long main thread task before the next paint.
Split the interaction into input delay, processing time, and presentation delay. This shows whether the user is waiting before JavaScript starts, inside JavaScript and related work, or before the visible paint.
Use Bottom Up and Call Tree to measure filtering, sorting, and result construction. Inspect style calculation, layout, paint, and invalidated areas to measure rendering cost.
Choose one experiment that matches the evidence. Reduce repeated computation first. Then reduce rendering scope with windowing or virtualization. Use scheduling only for work that can safely wait.
If filter and sort remain CPU heavy after reducing work, test a Web Worker. Send only the data needed for computation and return minimal result data. Keep DOM updates on the main thread.
Retest the same typing workload after each change. Compare p75 INP, the interaction distribution, long tasks, dropped frames, and trace shape.
Verify result count, top result order, empty and special queries, fast typing, paste behavior, focus, keyboard controls, and screen reader behavior.
Release cautiously, monitor field INP and errors, and use a feature flag or rollback path for material changes.
Practical Insights
With 8,000 records, scanning every record on every keystroke is roughly O(n) work for filtering. Sorting all matching records can add O(k log k), where k is the number of matches. If the code sorts the full list every time, the practical cost can approach O(n log n) for each input. Rebuilding thousands of DOM rows also creates large rendering and layout costs.
The first goal is to reduce the amount of work, not merely move it. Precomputed search fields use extra memory but reduce repeated string work. Caching can save CPU but needs invalidation when product data changes. Virtualization lowers DOM and layout cost but adds list state and accessibility complexity. A Web Worker can move suitable CPU work away from the main thread, but startup, message transfer, extra memory, cleanup, and maintenance add cost. Profiling itself also has overhead, so before and after comparisons must use the same conditions.
Why Interviewers Ask This
Interviewers ask this to see whether I can start from a real user symptom, split interaction latency into useful parts, use browser evidence to find the dominant cost, and choose a change that matches that evidence. They also want to see whether I understand that JavaScript work and rendering both compete for the browser main thread, that a Web Worker is only useful for suitable computation, and that faster results still need correct keyboard behavior, accessibility, and production verification.
Common interview mistakes
A common mistake is optimizing before measuring which part of INP is slow. Another is treating one local run, one Lighthouse score, or an average as final proof. It is also easy to compare different list sizes or typing patterns before and after a change, which makes the result unreliable.
Another mistake is assuming a Promise moves CPU work off the main thread. It does not. requestAnimationFrame also does not make expensive filtering free. Debouncing can reduce repeated work, but too much delay can make the search feel less responsive. A Web Worker can help with suitable CPU work, but moving filter and sort there does not remove DOM, layout, and paint cost on the main thread.
Another mistake is using Coverage or memory tools as proof of this specific interaction bottleneck without evidence. They can help investigate unused code or allocation problems if the trace points that way, but the Performance panel remains the main tool for this question.
Finally, do not improve the metric by breaking behavior. Result count, result order, focus, keyboard controls, screen reader announcements, paste behavior, and edge cases must remain correct. Also check whether the optimization simply moves the bottleneck from JavaScript to rendering, worker communication, or memory.
Interview tip
Explain the investigation as a chain of evidence. Start with p75 INP of 360 ms, split the interaction into input delay, processing time, and presentation delay, show how the Performance panel finds the dominant work, then connect each experiment to that evidence. Finish by saying that you will compare the same workload before and after and protect correctness, focus, keyboard behavior, accessibility, and field monitoring.
Interviewer may ask next
What if a local trace looks fast after the change, but field p75 INP is still poor?
I would not treat the local trace as proof that the problem is solved. The exact workload is typing into the filter over 8,000 client side records, and the production boundary is the real browser interaction through the next visible paint. I would segment field INP by browser, device class, list size, and interaction path, then reproduce the slow segment in the lab. The local machine may be faster, the production data may create more matches, or the bottleneck may have moved into layout, paint, worker communication, or another main thread task. The tradeoff is that broader telemetry and representative testing take more time, but they prevent us from optimizing only a fast lab case.
When would you move filtering and sorting to a Web Worker instead of only virtualizing the list?
I would use a Web Worker only if profiling shows that filter and sort remain a meaningful CPU cost on the main thread after reducing unnecessary work. For this 8,000 record search, virtualization mainly reduces DOM, layout, and paint work, while a worker targets suitable computation. If both are expensive, they can address different parts of the same interaction. The worker should receive only the data it needs and return minimal result data, because startup, message transfer, extra memory, cleanup, and code complexity are real costs. I would compare the same typing workload before and after and keep all DOM updates, focus behavior, and keyboard handling on the main thread.
85. How would you eliminate layout shifts caused by late page content?PerformanceMedium
i Question Details
A news page has a CLS of 0.31. Trace entries identify a header banner loaded after consent, image cards without intrinsic dimensions, and a promotional bar inserted above the article after an API response. Explain how you would map each shift to its source, distinguish expected user-triggered movement, reserve or relocate space, and validate the fix across narrow and wide viewports. Include a field-metric check rather than relying only on a visual inspection.
Short Interview Answer (30-60 seconds)
I would start from the CLS value of 0.31 and use the browser Performance panel and Layout Shift track to map each important shift to the element that moved. I would reserve space for the consent banner, give image cards intrinsic dimensions, and reserve or relocate the promotional area so it cannot push the article down after its API response arrives. I would separate shifts caused by recent user input from unexpected shifts. Then I would repeat the same checks on narrow and wide viewports and confirm the field CLS distribution, especially the 75th percentile.
Detailed Explanation
The page looks unstable because content appears late and pushes visible text or images to a new place. The starting CLS is 0.31. The goal is to find exactly which late elements create that movement and stop them from changing the page after it is visible. The three known causes are the consent banner, image cards that do not reserve space, and a promotion inserted above the article. The fix must work on both narrow and wide screens, and real visitor data must confirm that the page became stable.
Useful Questions to Ask the Interviewer
Should the header banner always have a known maximum height after consent?
Can the promotional content be shown below the article or inside a reserved area?
Which narrow and wide viewport sizes should we use for controlled checks?
Which field telemetry source is already available for CLS?
How to Explain It in an Interview
I would begin with the user visible symptom and baseline. This news page has a CLS of 0.31. I would reproduce the same page in a production build and record it with the browser Performance panel and Layout Shift track. For each important shift entry, I would inspect the affected elements and timing so the movement is tied to a measured source instead of guessed from visual inspection.
The trace already points to three causes. First, the header banner appears after consent and pushes the page down. I would reserve its final space before the banner content arrives. A placeholder or a container with a known minimum height or aspect ratio can keep the rest of the page in place while the real banner is swapped in.
Second, the image cards have no intrinsic dimensions. I would add width and height attributes or a stable CSS aspect ratio so the browser knows the image box size before the image finishes loading. The image can still use object fit for cropping, but the reserved box must remain stable while the image loads and decodes.
Third, the promotional bar arrives after an external API response and is inserted above the article. That creates unexpected movement because the article is already visible. I would either reserve a promotion slot from the start or move the promotion below the article or into another location where late arrival does not push existing content.
I would also distinguish expected movement from unexpected movement. CLS entries expose whether a shift had recent user input. A shift shortly after qualifying user input is not counted in CLS, while movement that appears later without such input is the problem I want to remove. I would still check keyboard navigation, focus behavior, and screen reader behavior so the layout change stays accessible.
For validation, I would retest the same route with the same production build and comparable device, network, and cache conditions. I would test at least one narrow viewport and one wide viewport because reserved space can behave differently at different widths. DevTools and Lighthouse are useful controlled diagnostic tools, but one lab run is not final proof.
After release, I would collect real user CLS through browser field telemetry using a Web Vitals library or PerformanceObserver where appropriate. I would compare the field distribution before and after the change, with special attention to the 75th percentile. I would also segment by viewport or device class so a good desktop result does not hide a narrow screen regression. CrUX or Search Console can provide additional Core Web Vitals field evidence when available.
The success target is field CLS below 0.1 at the 75th percentile across the important narrow and wide viewport groups, with no new regressions in correctness or accessibility. I would keep monitoring after release so a later banner, image, font, or promotion change does not bring the problem back.
Technical Approach
Start with the baseline CLS of 0.31 on the news page.
Reproduce the same page in a production build under controlled browser conditions.
Record the page with the Performance panel and Layout Shift track.
Map each important shift to the element that moved and the event that happened before it.
Check whether each shift had recent user input so expected movement is separated from unexpected movement.
Reserve the header banner space before consent finishes.
Give each image card stable width and height dimensions or a stable aspect ratio.
Reserve a promotional slot or relocate the promotion so the API response cannot push the article down.
Repeat the same checks on narrow and wide viewports under comparable conditions.
Verify visual behavior, keyboard behavior, focus behavior, and content correctness.
Compare real user CLS before and after the change, especially the 75th percentile.
Continue monitoring for regressions after release.
Practical Insights
The main cost is not algorithmic runtime. The browser work is small, but the page needs stable layout rules and careful testing across viewport sizes. Reserved space can leave some empty area when optional content does not appear. Moving a promotion can affect product or business placement. Field telemetry adds measurement and maintenance work. The important tradeoff is keeping the page stable without hiding content, breaking accessibility, or creating more empty space than the design can accept.
Why Interviewers Ask This
Interviewers want to see whether I can connect visible movement to measured browser evidence, separate expected movement from harmful movement, choose a targeted layout fix for each source, and prove the result with controlled browser checks plus real user data.
Common interview mistakes
Common mistakes are guessing the cause from visual inspection, using only one Lighthouse run, treating every movement as harmful, lazy loading images without reserving their dimensions, inserting a late promotion above visible content, comparing different viewport conditions before and after, and declaring success without field CLS data. Another mistake is reserving a size that works only for one viewport, which can create new shifts on narrow or wide screens. A team can also miss regressions by checking only an average instead of the field distribution and the 75th percentile.
Interview tip
Explain the answer as a measured sequence. Start from CLS 0.31, map each shift to one source, apply one matching layout change for that source, then prove the result on narrow and wide viewports with real user CLS data.
Interviewer may ask next
What if Lighthouse shows a good CLS after the fixes but field CLS is still poor?
I would trust the field result as evidence that the controlled test is missing an important real user condition. For this news page, I would segment field CLS by viewport, device class, page template, consent state, and other relevant client conditions, then reproduce the segment with the worst result. The hidden source could be a different banner size, image ratio, promotion timing, font change, or another late element that the lab run did not exercise. The tradeoff is that broader field analysis takes more time, but it prevents a narrow lab case from hiding a production problem.
What tradeoff would you consider if reserving space for optional banners leaves empty space for some users?
I would keep layout stability as the priority, but I would choose the smallest predictable slot that safely fits the real banner states on this news page. If the empty area is too costly, I would relocate the optional content to a place where late arrival does not push visible article content. I would test the choice on narrow and wide viewports and compare field CLS after release. The tradeoff is between visual stability and unused space, so the final choice should be based on measured layout behavior and product needs.
86. How would you reduce a long main-thread task without changing its output?PerformanceMedium
i Question Details
After a 2 MB JSON response arrives, a browser task spends 240 ms normalizing records, grouping them, and rendering summary rows; clicks during that interval feel delayed. Describe how you would profile scripting versus style and layout time, decide whether to reduce data, chunk work, schedule yielding, or move pure computation to a worker, and measure interaction improvement. Preserve record ordering and final DOM output, and account for cancellation if the user navigates away.
Short Interview Answer (30-60 seconds)
I would first record the slow interaction in the browser Performance panel and separate scripting time from style calculation, layout, and paint. If scripting dominates, I would remove unnecessary work first. Pure normalization and grouping can move to a Web Worker when they do not need the DOM. Work that must stay on the main thread can run in small batches with a yield between batches so clicks can run. I would preserve record order and the same final DOM, cancel obsolete work on navigation, then repeat the same workload and compare interaction latency and long tasks.
Detailed Explanation
A 2 MB set of information arrives, and the page spends about 240 milliseconds preparing it and showing summary rows. While that work is happening, a click can feel slow because the page cannot respond quickly. I would first find which part of the work consumes the time. Then I would remove work that is not needed, divide large work into smaller pieces, or move suitable calculations away from the busy page. Whatever choice I make, the records must stay in the same order and the final rows must stay exactly the same.
Useful Questions to Ask the Interviewer
Must every summary row appear together, or may rows be produced in small batches?
Can normalization and grouping run without reading from or changing the DOM?
Is all data in the 2 MB response required to produce the final summary rows?
Should navigation or a newer request cancel any unfinished processing?
How to Explain It in an Interview
I would begin with a controlled reproduction of the same 2 MB JSON workload. I would use the same page, production build, browser, device class, cache state, and interaction each time. In the browser Performance panel, I would record the delayed click and inspect the main thread. I would separate JavaScript execution from style calculation, layout, and paint. I would also follow the interaction from input arrival, through main thread waiting and handler work, to the next visible update.
The first decision comes from that evidence. If scripting takes most of the task, I would optimize the JavaScript path. If style calculation, layout, or paint is large, I would instead reduce DOM work, batch DOM changes, and avoid patterns that repeatedly force layout. I would not assume that the whole 240 milliseconds is computation without measuring it.
If some records or fields are not needed to produce the required summary rows, I would avoid processing that unnecessary data. This reduction is valid only when the final output remains identical. I would treat the remote data source as an external boundary rather than designing its internals.
If the remaining work must run on the main thread, I would process a small batch of records, save the progress and ordering information, yield control, and then continue with the next batch. Where supported, scheduler.yield() is a good way to let higher priority browser work run. A timer based yield can be used as a fallback. requestAnimationFrame is useful when the next step is tied to a visual update, but it does not make expensive computation free. The batch size should be measured rather than guessed because a batch that is still too large can remain a long task.
If normalization and grouping are pure computation and do not need the DOM, I would consider a Web Worker. The main thread sends the required data to the worker. The worker normalizes and groups the records while keeping their order. It sends the result back, and the main thread renders the summary rows. A worker uses a separate execution context, unlike a Promise, so CPU heavy work can stop blocking the main thread. The tradeoff is worker startup, message transfer, extra memory, cleanup, and more code. Large values may also be copied unless the chosen representation can be transferred.
DOM rendering still happens on the main thread. I would keep stable record keys and deterministic result ordering so chunking or worker processing cannot change the sequence of rows. I would compare the final grouping values and DOM output with the original implementation.
Cancellation is required because the work can become obsolete. I would create an AbortController for operations that support its signal. A chunk loop would check that signal between batches and stop when navigation or a newer request makes the work unnecessary. For a dedicated worker, I would terminate the worker when appropriate and ignore any result that belongs to an obsolete request. I would also remove related listeners and references during cleanup.
After the change, I would repeat exactly the same workload and interaction. I would compare interaction latency, the count and duration of long tasks, and the main thread trace before and after. PerformanceObserver can collect supported runtime entries such as long task or event timing data where the browser exposes them, but browser support must be checked. Field Web Vitals data can show whether real user interaction distributions improve. I would use several samples or percentiles rather than treating one local run as proof.
Finally, I would verify the same record ordering, grouping results, summary values, and final DOM output. I would also test keyboard use, screen reader behavior, navigation during processing, error cases, supported browsers, and memory behavior. The optimization succeeds only when the page becomes more responsive without changing the required result or moving the bottleneck somewhere else.
Technical Approach
Reproduce the delayed click with the same 2 MB JSON input and capture a baseline.
Record the interaction in the browser Performance panel.
Separate scripting from style calculation, layout, and paint.
Follow the interaction from input arrival through main thread waiting, handler work, rendering, and the next visible update.
If unnecessary data is being processed, remove only data that cannot affect the required final output.
If scripting dominates, remove repeated or unnecessary computation before adding concurrency.
If DOM related work dominates, batch DOM changes and reduce repeated style and layout work.
If work must stay on the main thread, process small batches and yield between them.
If normalization and grouping are pure computation, move that part to a Web Worker and keep DOM rendering on the main thread.
Keep deterministic record ordering and stable keys when combining batches or worker results.
Cancel obsolete work when navigation or a newer request occurs, and ignore late results.
Repeat the same workload and compare interaction latency and long task behavior.
Verify identical grouping, ordering, final DOM output, accessibility behavior, memory behavior, and error handling.
Practical Insights
If normalization and grouping visit each record once, their CPU cost usually grows with the number of records. Breaking that work into batches does not automatically reduce the total CPU work. Its main benefit is that the browser can handle input and rendering between batches. A Web Worker can keep suitable CPU heavy computation away from the main thread, but it adds startup, communication, memory, cleanup, browser support, and maintenance costs. Sending large data to a worker can also require copying. The best choice depends on measured computation time, rendering time, yield overhead, and worker communication cost.
Why Interviewers Ask This
Interviewers want to see whether I measure the browser delay before changing code, separate JavaScript work from rendering work, and choose an optimization that matches the measured bottleneck. They also want to see whether I understand when to reduce unnecessary work, when to divide work into smaller batches, when a Web Worker is appropriate, and how to prove that interaction improves without changing record order, final DOM output, cancellation behavior, or accessibility.
Common interview mistakes
A common mistake is optimizing before recording a trace. Another is assuming the entire 240 millisecond task is JavaScript without separating scripting from style calculation, layout, and paint. Using a Promise does not move CPU work away from the main thread. A Web Worker also cannot directly manipulate the DOM. Other mistakes include making chunks that are still too large, sending expensive copies to a worker without measuring communication cost, continuing obsolete work after navigation, allowing late results to update a newer page state, comparing different workloads before and after, trusting one local run as final proof, or improving responsiveness while changing record order or final DOM output.
Interview tip
Present this as a measurement driven decision. Start with the delayed click and the 240 millisecond task. Explain how you separate scripting from rendering work. Then choose data reduction, chunking with yielding, or a Web Worker based on that evidence. Finish with cancellation and the same before and after measurement, and state that ordering and final DOM output must remain identical.
Interviewer may ask next
What would you do if the Performance panel showed that style calculation and layout, rather than normalization and grouping, were the main source of the 240 millisecond delay?
I would optimize DOM and rendering work instead of moving the main optimization to a Web Worker. For the same 2 MB JSON interaction, I would inspect the main thread trace for repeated style calculation, layout, and DOM updates. I would batch DOM writes, avoid repeated read and write patterns that force layout, and reduce unnecessary row updates while keeping the final DOM identical. Then I would repeat the same workload and compare interaction latency and rendering work. The tradeoff is additional rendering logic, so I would verify ordering, visual behavior, accessibility, and correctness.
When would you choose main thread chunking instead of moving normalization and grouping to a Web Worker?
I would choose main thread chunking when the work needs DOM access, when the pure computation is not large enough to justify worker startup and message transfer, or when worker support and maintenance cost are not worthwhile. For the same 2 MB workload, I would process small batches and yield between them while keeping deterministic record order. I would compare this with the worker approach under the same measurement conditions. Chunking still consumes main thread CPU, while a worker adds communication, memory, startup, and cleanup cost.
87. How would you find and fix layout thrashing in a drag interaction?PerformanceMedium
i Question Details
During pointer movement, a widget loops through 300 elements, reads getBoundingClientRect(), writes style.transform, and repeats those operations in the same loop. The performance trace shows repeated forced layout warnings and dropped frames. Explain how to prove the read/write dependency, reorganize measurement and mutation phases, schedule updates, and test that hit detection remains correct after scrolling or resizing. Include teardown of listeners and pending animation callbacks.
Short Interview Answer (30-60 seconds)
I would first record the same drag in the browser Performance panel and inspect the pointermove work. I would look for repeated forced reflow warnings, layout activity, long main thread work, and dropped frames. Then I would prove the dependency in the loop: getBoundingClientRect reads geometry, style.transform writes visual state, and the next geometry read may force the browser to flush pending style and layout work. I would collect the required rectangles first, perform the transform writes afterward, and let pointermove only save the latest pointer position and schedule one requestAnimationFrame callback. I would retest the same drag, verify hit detection after scroll and resize, and remove listeners and cancel any pending animation frame during teardown.
The user sees a drag that feels slow or jumps while moving. The page checks the position of 300 items and changes their appearance again and again during the same movement. This can make the browser stop repeatedly to work out where things are before it can continue. I would first prove that this repeated checking and changing is causing the slow frames. Then I would group all checking together, group all changes together, update only when the screen is ready, and test that the correct item is still found after the page moves or changes size.
Useful Questions to Ask the Interviewer
Should hit detection use viewport coordinates throughout the interaction?
Can the 300 element rectangles stay cached during one drag, or can their geometry change while dragging?
Must scrolling while dragging be supported?
Can content changes or zoom change the target geometry during the drag?
How to Explain It in an Interview
I would begin with the visible symptom and a repeatable baseline. The drag drops frames, and the Performance trace shows repeated forced reflow warnings during pointermove. I would reproduce the same drag using the same browser, device class, production build, page state, and interaction path. On a 60 Hz display, a frame has about 16.7 milliseconds available, so repeated long work inside one frame can make the interaction visibly stutter.
Next, I would inspect one pointermove event in the Performance panel. The important pattern is read, write, read, write across the 300 elements. getBoundingClientRect asks the browser for current geometry. style.transform changes visual state. A transform normally avoids changing document layout itself, but a later geometry read that requires current information can still make the browser flush pending style and rendering work before returning the result. Repeating this dependency inside the loop is the layout thrashing pattern I want to remove.
I would reorganize the work into two phases. The measurement phase performs every required getBoundingClientRect call and stores the rectangles. No DOM style writes happen during that phase. The mutation phase then uses the stored values and performs the style.transform writes. No new geometry reads happen inside that write phase. This changes the pattern from alternating reads and writes to grouped reads followed by grouped writes.
I would also schedule the interaction carefully. pointermove can fire more often than the browser paints. The pointermove handler should only store the newest clientX and clientY values and request an animation frame when one is not already pending. The requestAnimationFrame callback performs the needed read phase and then the write phase. requestAnimationFrame does not make expensive work free. Its benefit here is that many pointer events can share one visual update before the next paint.
For hit detection, I would use one coordinate system. getBoundingClientRect returns viewport relative rectangles, so clientX and clientY can be compared directly with those rectangles. If the application instead converts rectangles to page coordinates, it must convert the pointer coordinates in the same way by using the current scroll offsets. Mixing page coordinates and viewport coordinates would produce incorrect targets.
Cached measurements need clear invalidation rules. At pointerdown I would collect the required rectangles. If scrolling, resizing, zooming, or relevant content changes can move the target geometry, I would mark the measurements stale and collect them again before the next hit test that needs fresh geometry. If the visual transforms themselves are meant to change the hit regions, I would also refresh the relevant rectangles before using them again. The exact cache lifetime depends on what the application considers a valid hit area.
After the change, I would repeat the same Performance recording with the same drag path. I would compare forced reflow warnings, layout activity, long main thread work, and frame timing. I would expect the alternating layout pattern to disappear or become much smaller, with reads grouped before writes. I would not claim an improvement percentage without measurement.
Correctness is part of the verification. I would drag while scrolling, resize the window, and test representative pointer positions to confirm that the expected target is still selected. I would also check that the drag remains visually correct and that the optimization did not move the bottleneck into another part of the rendering path.
Finally, I would tear down everything created for the interaction. I would remove pointer, scroll, and resize listeners, cancel a pending requestAnimationFrame callback, clear cached rectangles and drag state, and disconnect any observer that was created to invalidate geometry. This prevents orphan work and unnecessary retained references.
Key Insight / Why This Solution Works
Reproduce the exact drag while recording the browser Performance panel.
Keep the browser, device class, build, page state, and drag path the same for the baseline and retest.
Inspect pointermove and confirm repeated forced reflow warnings, layout activity, long main thread work, or dropped frames.
Trace the dependency across the 300 elements: read geometry, write transform, then read geometry again.
Move all required getBoundingClientRect calls into one measurement phase and store the results.
Move all style.transform changes into a later mutation phase that performs no geometry reads.
Make pointermove store only the newest pointer coordinates and request one animation frame when no callback is already pending.
In the animation callback, perform the required reads first and the writes second.
Keep hit testing in one coordinate system. Use viewport rectangles with clientX and clientY, or convert both rectangles and pointer coordinates consistently.
Mark cached rectangles stale when scrolling, resizing, zooming, content changes, or other relevant geometry changes occur.
Repeat the same performance recording and compare the same rendering evidence.
Test hit detection while scrolling and after resizing.
Remove all listeners, cancel a pending animation callback, clear cached state, and disconnect any observer during teardown.
Code
functioncreateDragController(elements, dragHandle, computeTransform) {
let pointerX = 0;
let pointerY = 0;
let dragging = false;
let scheduled = false;
let rafId = null;
// Cached viewport rectangles are reused until a relevant geometry change marks them stale.let rects = [];
let rectsDirty = true;
functionmeasureRects() {
// Read phase: collect every required DOM geometry value before any style mutation.
rects = elements.map((element) => element.getBoundingClientRect());
rectsDirty = false;
}
functionfindHitIndex(clientX, clientY) {
// DOMRect values and client coordinates are both relative to the viewport.for (let index = 0; index < rects.length; index += 1) {
const rect = rects[index];
if (
clientX >= rect.left &&
clientX <= rect.right &&
clientY >= rect.top &&
clientY <= rect.bottom
) {
return index;
}
}
returnnull;
}
functionupdate() {
// Clear scheduling state when this visual update begins.
scheduled = false;
rafId = null;
if (!dragging) {
return;
}
// Refresh measurements only when geometry may have changed.if (rectsDirty || rects.length !== elements.length) {
measureRects();
}
// Hit detection uses cached JavaScript data and causes no new DOM geometry read here.const hitIndex = findHitIndex(pointerX, pointerY);
// Prepare all transform strings before starting the DOM write phase.const transforms = elements.map((element, index) => {
returncomputeTransform({
element,
index,
rect: rects[index],
pointerX,
pointerY,
hitIndex,
});
});
// Write phase: apply styles only after every required geometry read is complete.
elements.forEach((element, index) => {
element.style.transform = transforms[index];
});
}
functionscheduleUpdate() {
// Allow at most one pending visual update even when many pointer events arrive.if (scheduled) {
return;
}
scheduled = true;
rafId = requestAnimationFrame(update);
}
functiononPointerDown(event) {
// Start the measurement window for a new drag and capture the first pointer position.
dragging = true;
pointerX = event.clientX;
pointerY = event.clientY;
rectsDirty = true;
scheduleUpdate();
}
functiononPointerMove(event) {
// Keep this handler lightweight by storing only the latest input state.if (!dragging) {
return;
}
pointerX = event.clientX;
pointerY = event.clientY;
scheduleUpdate();
}
functiononPointerUp() {
// Stop drag work and cancel a visual update that has not run yet.
dragging = false;
if (rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
scheduled = false;
}
functiononGeometryChange() {
// Scroll or resize can invalidate viewport rectangles used for hit detection.
rectsDirty = true;
if (dragging) {
scheduleUpdate();
}
}
dragHandle.addEventListener('pointerdown', onPointerDown);
window.addEventListener('pointermove', onPointerMove);
window.addEventListener('pointerup', onPointerUp);
window.addEventListener('scroll', onGeometryChange, { passive: true });
window.addEventListener('resize', onGeometryChange);
returnfunctiondestroy() {
// Remove every listener installed by this controller.
dragHandle.removeEventListener('pointerdown', onPointerDown);
window.removeEventListener('pointermove', onPointerMove);
window.removeEventListener('pointerup', onPointerUp);
window.removeEventListener('scroll', onGeometryChange);
window.removeEventListener('resize', onGeometryChange);
// Cancel orphan animation work that may still be waiting for the next paint.if (rafId !== null) {
cancelAnimationFrame(rafId);
}
// Release cached references and reset interaction state.
rafId = null;
scheduled = false;
dragging = false;
rects = [];
rectsDirty = true;
};
}
Why Interviewers Ask This
Interviewers ask this to see whether I can prove a browser rendering problem before changing code. They want to know whether I understand the dependency between geometry reads and style writes, how repeated reads after pending writes can force synchronous browser work, and how to reorganize the interaction into measurement and mutation phases. They also want to see whether I can schedule visual work with requestAnimationFrame, keep hit detection correct after scrolling or resizing, compare the same workload before and after the change, and remove listeners and pending animation callbacks during teardown.
Common interview mistakes
One mistake is changing code before proving that repeated rendering synchronization is the real bottleneck. Another is moving the original mixed read and write loop into requestAnimationFrame and assuming the problem is solved. requestAnimationFrame controls scheduling, but it does not remove a read and write dependency inside the callback. Another mistake is caching rectangles without invalidating them after relevant scrolling, resizing, zooming, content changes, or visual changes that alter the intended hit areas. Mixing viewport coordinates from getBoundingClientRect with page coordinates also breaks hit detection. Comparing a different drag workload before and after weakens the evidence. Finally, leaving listeners or a pending animation callback active after teardown can retain references and perform unnecessary work.
Interview tip
Explain the evidence and dependency first. Show that the trace contains repeated rendering work around an alternating geometry read and style write pattern. Then state the targeted change: batch all reads, batch all writes, and schedule at most one visual update with requestAnimationFrame. Finish with the same before and after measurement, hit detection tests after scroll and resize, and complete listener and animation callback cleanup.
Interviewer may ask next
What would you check if forced reflow warnings fall but hit detection becomes wrong while scrolling during the drag?
I would treat that as a failed optimization because the exact workload includes correct hit detection during the same drag. getBoundingClientRect returns viewport relative rectangles, and clientX and clientY are also viewport relative. If cached rectangles become stale when scrolling changes the target positions, I would mark the cache stale on the relevant scroll event and refresh it before the next required hit test. If scrolling happens inside a nested scroll container, I would observe that container rather than relying only on window scrolling. The tradeoff is measurement frequency. More frequent measurement costs more main thread time, while stale measurements can select the wrong target.
Would a Web Worker be a better solution if the drag is still slow after the reads and writes are batched?
Not for the DOM measurement and style mutation part of this workload. getBoundingClientRect and style.transform require access to the browser document and must remain on the main thread. I would record the same drag again and identify the remaining cost before changing the concurrency model. If a separate pure JavaScript calculation becomes the measured bottleneck, that calculation may be suitable for a Web Worker. The tradeoff is worker startup, message transfer, extra memory, synchronization, and maintenance complexity. I would not move work to a worker unless the trace shows that independent computation is now the important bottleneck.
88. Design privacy-conscious real-user monitoring for Core Web Vitals and custom interactions.PerformanceHard
i Question Details
Design a browser-side measurement plan for a multi-route application that records LCP, INP, CLS, navigation timing, route identity, release version, and two named business interactions. Specify which PerformanceObserver entry types are consumed, how buffered entries and page lifecycle events are handled, how values are attributed without capturing user content, how sampling and batching work, and what is sent when a page is hidden. Define aggregation by percentile and a validation method against browser tooling, including unsupported-browser behavior.
Short Interview Answer (30-60 seconds)
I would measure real users in the browser with PerformanceObserver and the Performance API. I would collect LCP, INP, CLS, navigation timing, the active route template, release version, and the named add_to_cart and checkout_start interactions. I would keep only privacy safe attributes, sample deterministically once per session, batch records in bounded memory, and flush the final unsent batch when the page becomes hidden. I would aggregate by route, release, and device class using p50, p75, and p95, then validate raw entries with browser tools. The main tradeoff is collecting enough detail for diagnosis without collecting user content or adding too much measurement overhead.
Detailed Explanation
This design measures how real people experience the application while protecting their content. It records when the main content appears, how quickly the page responds to input, whether the layout moves unexpectedly, and how long document navigation takes. It also measures two important actions, add_to_cart and checkout_start. Each record uses a safe route pattern and release version instead of personal information. Only a selected set of sessions is measured. Records are grouped in memory, sent when needed, summarized with percentiles, and checked against browser tools before the data is trusted.
Useful Questions to Ask the Interviewer
Should every route use the same sampling rate, or can important routes use a higher rate?
Which route templates and release fields are already available in the frontend?
Should add_to_cart and checkout_start end after the next visible result, as shown in the approved design?
Which browsers must provide the full metric set, and which browsers may report a reduced set?
How to Explain It in an Interview
I would start with the exact user experience we need to measure. For page experience, I would collect LCP, INP, CLS, and document navigation timing. For business experience, I would collect add_to_cart and checkout_start. The browser is the measurement boundary. The collector is an external destination for finished records, not part of the browser performance model.
For PerformanceObserver, I would consume largest contentful paint entries, event entries for INP, layout shift entries for CLS, navigation entries, and measure entries for the two custom interactions. I would request buffered entries where that entry type and browser support it so early observations are not missed. For LCP, I would keep the latest valid candidate until the metric is finalized. For CLS, I would use layout shift entries without recent user input and apply the CLS session window calculation rather than treating every shift across the whole page lifetime as one total. For INP, I would use PerformanceEventTiming interaction entries and derive the interaction latency according to the browser metric rules.
Navigation Timing gives document level values such as TTFB, DOMContentLoaded, and load event end. In a multi route application, the route template identifies the active application route, but a client route change does not magically create a new document Navigation Timing entry. I would keep that distinction clear when analyzing route data. The diagram also shows Resource Timing as optional loading context. If I use it, I would keep only safe timing and size information and would not send full resource URLs.
The two business interactions use performance.mark() and performance.measure(). I would place a start mark when the user begins add_to_cart or checkout_start. I would place the end mark after the visible result is produced, then store the measured duration under that fixed interaction name. I would never store button text, form values, selectors, element text, query strings, or other user content.
Each record would include a route template such as /products/:id, release version, coarse device and viewport information, a random per tab session_id, a sampled flag, metric values, and capability flags. It would not include an account identifier, user identifier, full URL, query string, text value, selector, or element content. This gives enough attribution to compare routes and releases without identifying a person.
Sampling would be deterministic for the whole page session. For example, I can hash session_id and keep about ten percent of sessions. Once a session is selected or rejected, that decision stays stable for that session. Stable sampling avoids changing the population halfway through a page session and makes the resulting distributions easier to reason about.
Selected records go into a small in memory ring buffer. The buffer has a fixed cap so instrumentation cannot grow without limit. I would flush when the batch reaches a chosen record count, when the time since the previous flush reaches a limit, or when document visibility changes to hidden. The primary final flush signal is visibilitychange when the document becomes hidden. pagehide is the fallback lifecycle signal.
When the page becomes hidden, I would send the final unsent batch with navigator.sendBeacon(). If sendBeacon is unavailable, I would use fetch with keepalive set to true. The payload contains only the privacy safe fields shown in the design. Sending must not block navigation or unload. I would also remember that sendBeacon accepting data for queuing is not a guarantee that the collector received it, so the monitoring system should tolerate missing batches.
For reporting, I would group records by route template, release version, device class, and only other coarse dimensions that are safe and useful. I would compute p50, p75, and p95 for LCP, INP, CLS, navigation timings, add_to_cart, and checkout_start. P75 is especially useful for Core Web Vitals, while the other percentiles help show the overall distribution. Percentiles are more useful than one average because a slow group of users remains visible.
For validation, I would run the same local route and interaction scenario in Chrome DevTools Performance. I would compare raw largest contentful paint, layout shift, event, navigation, and measure entries with what the RUM code records. Lighthouse can provide repeatable load diagnostics, but it is not production field evidence. I would compare field distributions with lab results as a trend and consistency check rather than expect exact percentile equality.
Browser support is handled with feature detection. I would test PerformanceObserver and the individual entry types that matter. If one metric is unsupported, I would still collect the supported metrics, keep Navigation Timing and custom marks and measures when available, and include capability flags in the payload. An unsupported metric should be absent rather than reported as zero because zero could look like a genuine measurement. Missing support must never block the page.
The main production tradeoff is diagnostic detail versus privacy, cost, and observer overhead. More dimensions can help analysis, but they increase payload size, cardinality, and privacy risk. A higher sample rate improves confidence, but it increases browser work and collection volume. I would therefore start with the smallest useful schema, stable session sampling, bounded memory, explicit capability flags, and the exact measurements shown in the approved diagram. I would then verify that the instrumentation itself does not materially change the experience it measures.
Technical Approach
Define the browser measurement boundary and the exact metrics: LCP, INP, CLS, navigation timing, add_to_cart, and checkout_start.
Observe largest contentful paint, event, layout shift, navigation, and measure entries. Request buffered entries where that entry type and browser support it.
Derive the final metric values. Keep the latest valid LCP candidate. Apply the CLS session window rule to eligible layout shifts. Derive INP from PerformanceEventTiming interaction entries.
Measure add_to_cart and checkout_start with performance.mark() and performance.measure().
Add only safe attribution: route template, release version, coarse device and viewport data, random per tab session_id, sampled flag, and capability flags.
Choose sessions deterministically, for example about ten percent by hashing session_id, and keep that decision stable for the session.
Store selected records in a bounded in memory ring buffer. Flush by batch size, elapsed time, or hidden page state.
Use visibilitychange to hidden as the primary final flush signal and pagehide as a fallback.
Send the final unsent batch with navigator.sendBeacon(). Use fetch with keepalive when sendBeacon is unavailable.
Group field records by safe dimensions and compute p50, p75, and p95 for each metric.
Validate raw entries with Chrome DevTools Performance and use Lighthouse only as a controlled diagnostic check.
Feature detect browser support, send capability flags, and collect a reduced metric set when some entry types are unavailable.
Practical Insights
The browser work should stay small and bounded. Processing cost grows with the number of performance entries and custom interaction records that are actually collected. Memory stays bounded because the ring buffer has a fixed maximum size. Sampling lowers total collection volume, and batching lowers the number of network sends. The main costs are observer callbacks, small in memory records, serialization, payload bytes, browser support logic, and maintenance. A larger sample improves confidence but costs more browser and collection capacity. More attribution fields may help diagnosis, but they increase payload size, data cardinality, and privacy risk.
Why Interviewers Ask This
Interviewers ask this to see whether you can design browser measurement that is useful, private, reliable, and cheap enough to run for real users. They want to know if you understand Core Web Vitals, PerformanceObserver, the Performance API, page lifecycle events, safe attribution, deterministic sampling, bounded batching, percentile reporting, browser support limits, and validation with browser tools. They also want to see whether you can separate field evidence from controlled lab checks without collecting user content.
Common interview mistakes
Common mistakes include collecting full URLs, query strings, text values, selectors, or account identifiers when a route template and named interaction are enough. Another mistake is changing the sampling decision during one session, which can bias the population. Do not rely only on beforeunload for delivery. Do not treat sendBeacon queue acceptance as proof of delivery. Do not report unsupported metrics as zero. Do not calculate CLS by simply adding every layout shift for the entire page lifetime. Do not treat Lighthouse as production proof or expect one lab run to equal field percentiles. Do not use only averages when the slow part of the distribution matters. Do not allow the in memory buffer to grow without a cap. Finally, do not add so much instrumentation that the monitoring code changes the performance it is trying to observe.
Interview tip
Explain the design in one path: collect, attribute safely, sample, batch, send on hidden, aggregate by percentile, validate, then handle missing browser support. Name add_to_cart and checkout_start, the route template, and the exact privacy boundary. Make it clear that field data shows real user distributions while DevTools and Lighthouse help reproduce and validate controlled cases.
Interviewer may ask next
What if field p75 for INP is poor, but your local DevTools run looks fast?
I would not conclude that the field data is wrong. The exact workload is INP for real interactions on the measured route templates, while the local DevTools run is only one controlled browser scenario. I would first confirm that PerformanceEventTiming entries and interaction attribution match in DevTools. Then I would segment field results by route, release, and device class. The slow field tail may come from devices, interaction patterns, or main thread work that the local scenario does not reproduce. The tradeoff is that more segmentation can reveal the cause, but it also creates smaller sample groups and higher analysis cardinality.
How would you change the design if traffic grows enough that sampling ten percent of sessions becomes too expensive?
I would keep the same browser measurement boundary, metric definitions, privacy rules, and bounded batching, then lower the deterministic session sample for very high traffic groups. I would keep the sampling decision stable for each session and compare percentile stability before and after the change for LCP, INP, CLS, add_to_cart, and checkout_start. Important low traffic routes could keep a higher sample while very busy routes use a lower rate. The tradeoff is lower collection cost versus less statistical confidence in small groups, so I would change the rate only after checking sample volume and percentile stability.
89. How would you measure performance across client-side navigations in a single-page application?PerformanceHard
i Question Details
The initial document navigation is fast, but users report slow route transitions that do not create new Navigation Timing entries. Define start and completion boundaries for a route change that may involve code loading, data fetching, DOM updates, images, and a final paint. Explain how you would instrument user-initiated and programmatic navigations, exclude aborted or superseded transitions, relate long tasks and layout shifts to the route, and compare the custom metric with initial-load Core Web Vitals.
Short Interview Answer (30-60 seconds)
I would treat every completed route change as its own measured transaction. I would mark T0 when the user or application requests the route, assign a unique navigation ID, and mark T1 at an application owned visual completion proxy after required route content is committed and the next rendering opportunity is reached. I would discard superseded transitions, associate long tasks and layout shifts with that route window, collect resource and interaction data, and compare P50, P75, and P95 route results with initial load LCP, INP, and CLS. The important tradeoff is that T1 is a custom proxy, not a standard browser paint metric.
Users may see a fast first visit but still feel that moving to another view is slow. The goal is to measure every route change from the moment it starts until the new view is ready enough to see and use. We also need to ignore a route change if a newer one replaces it. Then we can see which routes are slow, what work happened while they were changing, and how many real users are affected. Finally, we compare these route results with the first visit results without pretending they are the same measurement.
Useful Questions to Ask the Interviewer
What should count as visually complete for each route?
Do we need to wait for every image, or only content required for the route?
Which browsers, device classes, and network conditions matter most?
Should back and forward navigation use the same completion rule?
Which routes have the most user complaints?
How to Explain It in an Interview
I would start with the symptom. The initial document navigation is fast, but users report slow route changes. A single page application normally keeps the same document, so those route changes do not create new Navigation Timing entries. I therefore need a custom route measurement.
At T0, I mark the moment the route change is requested. For a click or a back and forward action, I capture the user intent at the router boundary. For a programmatic navigation such as router.push or router.replace, I mark the same logical start when the application requests the route. Every transition receives a unique navigation ID so marks, resources, tasks, layout shifts, interactions, and completion can be related to the same route.
Between T0 and T1, I collect the work that can make the transition slow. This can include dynamic JavaScript loading, route data requests, state changes, DOM updates, images, fonts, style calculation, layout, paint preparation, and main thread work. Resource Timing helps explain code, data, image, and font loading. PerformanceObserver can collect supported long task, layout shift, and interaction entries. Application marks can record useful milestones such as data ready or DOM committed.
For T1, I would not claim that the browser exposes a standard final paint event for a route. The application defines a visual completion proxy. After the required route content is ready and committed, I use requestAnimationFrame as a practical next rendering opportunity and then record routeComplete. The route duration is T1 minus T0. This gives the team a repeatable boundary, but requestAnimationFrame itself does not prove that pixels have already been presented to the user.
If a newer navigation begins before the previous transition reaches T1, I mark the older navigation as superseded and exclude it from completed route duration results. The navigation ID prevents events from the old transition from being mixed with the new transition.
For long tasks, I associate entries that overlap the T0 to T1 window. I record their overlap or blocking contribution and any available attribution details. For layout shifts, I associate entries whose start time is inside the route window and ignore entries where hadRecentInput is true. I call the result a route window layout shift total, not standard page CLS. For interactions inside the window, I can record a custom worst interaction latency, but I do not call that route INP because INP is a standard page level Core Web Vital.
I would collect field telemetry by route, browser, device class, network condition, build version, and cache state. I would compare P50, P75, and P95 for route duration and supporting signals. In a controlled lab case, I would reproduce the same route and use the browser Performance tools and Network tools to see whether the delay comes mainly from loading, JavaScript execution, rendering, or interaction blocking.
Initial load measurements remain separate. Navigation Timing describes the document navigation. LCP, INP, and CLS come from their own performance entries and describe Core Web Vitals for the document experience. I can compare distributions and regressions between initial load metrics and custom route metrics, but the custom metrics do not replace Core Web Vitals.
If the evidence points to slow chunk loading, heavy JavaScript, delayed data, excessive rendering, or expensive assets, I change only that measured cause. Then I repeat the same route, browser, device class, network condition, build mode, and cache state. I verify the target metric, visual behavior, focus behavior, keyboard and screen reader behavior, errors, and nearby routes. After release, I watch the same field distributions and error signals to make sure the problem improved and the bottleneck did not move elsewhere.
Key Insight / Why This Solution Works
Define the exact route, browser, device class, network condition, build mode, cache state, and visual completion rule.
Mark T0 when user intent or a programmatic route request begins.
Assign a unique navigation ID to the transition.
Record route milestones plus relevant resource, long task, layout shift, and interaction entries.
After required route content is ready and committed, use requestAnimationFrame as the practical next rendering opportunity proxy and mark T1.
If a newer route begins before T1, mark the previous transition as superseded and exclude it from completed route results.
Compute route duration as T1 minus T0 and associate supporting signals with the same navigation ID.
Aggregate real user results by route and conditions and compare P50, P75, and P95.
Reproduce slow routes in browser Performance and Network tools to classify the delay as loading, scripting, rendering, or interaction work.
Make one evidence based change, repeat the same scenario, and verify correctness, accessibility, errors, and nearby routes.
Code
const active = newMap();
let currentNavigationId = null;
let nextId = 1;
const longTasks = [];
const layoutShifts = [];
const interactionEntries = [];
// Collect supported browser entries once so they can later be related to a route window.if ('PerformanceObserver'inwindow) {
try {
const longTaskObserver = newPerformanceObserver((list) => {
for (const entry of list.getEntries()) {
longTasks.push({
startTime: entry.startTime,
duration: entry.duration,
});
}
});
longTaskObserver.observe({ type: 'longtask', buffered: true });
} catch {}
try {
const layoutShiftObserver = newPerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
layoutShifts.push({
startTime: entry.startTime,
value: entry.value,
});
}
}
});
layoutShiftObserver.observe({ type: 'layout-shift', buffered: true });
} catch {}
try {
const eventObserver = newPerformanceObserver((list) => {
for (const entry of list.getEntries()) {
interactionEntries.push({
startTime: entry.startTime,
duration: entry.duration,
});
}
});
eventObserver.observe({ type: 'event', buffered: true, durationThreshold: 16 });
} catch {}
}
functiondiscardNavigation(nav) {
// Remove app owned marks for a transition that will never be reported as completed.
performance.clearMarks(nav.startMark);
for (const milestone of nav.milestones) {
performance.clearMarks(`route:${nav.id}:${milestone.name}`);
}
active.delete(nav.id);
}
exportfunctionbeginRouteNavigation(route) {
// A newer route supersedes the previous active transition, so the old duration is excluded.if (currentNavigationId !== null) {
const previous = active.get(currentNavigationId);
if (previous) {
previous.status = 'superseded';
discardNavigation(previous);
}
}
const id = nextId++;
const startMark = `route:${id}:start`;
const startTime = performance.now();
// T0 is the logical navigation intent boundary for user and programmatic navigations.
performance.mark(startMark, { startTime });
active.set(id, {
id,
route,
startMark,
startTime,
status: 'active',
milestones: [],
});
currentNavigationId = id;
return id;
}
exportfunctionmarkRouteMilestone(id, name) {
const nav = active.get(id);
if (!nav || nav.status !== 'active') return;
// App owned milestones connect data, DOM, or asset readiness to the same navigation ID.const markName = `route:${id}:${name}`;
performance.mark(markName);
nav.milestones.push({
name,
time: performance.now(),
});
}
functionnextRenderingOpportunity() {
// The callback is a practical next rendering opportunity proxy, not a confirmed paint timestamp.returnnewPromise((resolve) =>requestAnimationFrame(resolve));
}
exportasyncfunctioncompleteRouteNavigation(id) {
const nav = active.get(id);
if (!nav || nav.status !== 'active' || currentNavigationId !== id) {
returnnull;
}
// Call this only after the application knows that required route content is ready and committed.awaitnextRenderingOpportunity();
if (!active.has(id) || nav.status !== 'active' || currentNavigationId !== id) {
returnnull;
}
const endMark = `route:${id}:complete`;
const measureName = `route:${id}:duration`;
const endTime = performance.now();
// T1 is an application owned visual completion proxy, not a standardized browser paint metric.
performance.mark(endMark, { startTime: endTime });
performance.measure(measureName, nav.startMark, endMark);
const durationEntry = performance.getEntriesByName(measureName).at(-1);
// Associate long tasks by temporal overlap with the route measurement window.const routeLongTasks = longTasks
.filter((entry) => {
const taskEnd = entry.startTime + entry.duration;
return entry.startTime < endTime && taskEnd > nav.startTime;
})
.map((entry) => {
const overlapStart = Math.max(entry.startTime, nav.startTime);
const overlapEnd = Math.min(entry.startTime + entry.duration, endTime);
return {
startTime: entry.startTime,
duration: entry.duration,
overlap: Math.max(0, overlapEnd - overlapStart),
};
});
// This is a custom route window total, not standard page CLS.const routeLayoutShift = layoutShifts
.filter((entry) => entry.startTime >= nav.startTime && entry.startTime <= endTime)
.reduce((sum, entry) => sum + entry.value, 0);
// This is a custom worst interaction latency inside the route window, not standard INP.const routeInteractionLatency = interactionEntries
.filter((entry) => entry.startTime >= nav.startTime && entry.startTime <= endTime)
.reduce((worst, entry) =>Math.max(worst, entry.duration), 0);
// Resource Timing gives route related loading evidence for code, data, images, fonts, and other assets.const resources = performance
.getEntriesByType('resource')
.filter((entry) => entry.startTime >= nav.startTime && entry.startTime <= endTime)
.map((entry) => ({
name: entry.name,
initiatorType: entry.initiatorType,
startTime: entry.startTime,
duration: entry.duration,
transferSize: entry.transferSize,
}));
nav.status = 'completed';
if (currentNavigationId === id) {
currentNavigationId = null;
}
const result = {
navigationId: id,
route: nav.route,
routeDuration: durationEntry?.duration ?? endTime - nav.startTime,
routeInteractionLatency,
routeLayoutShift,
longTasks: routeLongTasks,
resources,
milestones: nav.milestones,
};
// Clear app owned marks and measures after the summary is built to limit retained entries.
performance.clearMarks(nav.startMark);
performance.clearMarks(endMark);
for (const milestone of nav.milestones) {
performance.clearMarks(`route:${id}:${milestone.name}`);
}
performance.clearMeasures(measureName);
active.delete(id);
return result;
}
Why Interviewers Ask This
Interviewers ask this to see whether I can create a useful browser measurement when normal document navigation timing does not cover route changes. They want to know if I can define clear start and completion boundaries, connect browser work to the correct route, discard misleading samples, choose suitable browser APIs, use real user distributions, and compare custom route measurements with standard Core Web Vitals without confusing the two.
Common interview mistakes
Common mistakes are assuming that every route change creates a new Navigation Timing entry, calling requestAnimationFrame a confirmed final paint timestamp, reporting superseded transitions as successful samples, and calling a route window layout shift total CLS. Another mistake is calling the worst route interaction INP even though INP is a standard page level metric. Results are also misleading when teams compare different routes or different device and network conditions, use averages only, profile one fast development computer, optimize before measuring, or treat one profiler run as proof of production behavior.
Interview tip
Start with the measurement boundary. Say that T0 is navigation intent and T1 is an application owned visual completion proxy. Then explain the navigation ID, the superseded transition rule, the supporting browser entries, field percentiles, and the difference between custom route metrics and standard Core Web Vitals. This gives the interviewer a clear end to end measurement story without claiming that the browser provides a standard route paint metric.
Interviewer may ask next
What if requestAnimationFrame runs while a large route image is still loading?
Then the completion rule is too early for that route. I would first define which content is required for the route to count as visually complete. completeRouteNavigation would be called only after that required image or other required asset is ready and its related UI is committed. T1 would still use the next rendering opportunity as the application owned proxy. Optional images should not block the metric unless the product definition requires them. The tradeoff is that waiting for too much content can turn a useful readiness metric into an asset completeness metric.
How would you roll this measurement out without creating too much telemetry cost?
I would keep the same T0 to T1 route boundary and navigation ID, but sample real user telemetry and send compact summaries instead of every raw browser entry. I would keep route duration, custom route interaction latency, route window layout shift, long task contribution, resource summaries, route name, browser, device class, network condition, build version, and cache state. I would compare P50, P75, and P95 across releases. The tradeoff is less raw debugging detail in exchange for lower network, storage, and analysis cost.
90. How would you restore back-forward cache eligibility without breaking page state?PerformanceHard
i Question Details
A commerce application reloads whenever users press Back, and DevTools reports that pages are excluded from the back-forward cache because of an unload handler and an open cross-page communication resource. Describe how you would confirm every blocker, replace incompatible lifecycle logic, pause and resume timers or connections around pagehide and pageshow, and verify restored state after a persisted navigation. Include tests that distinguish a bfcache restore from a normal reload.
Short Interview Answer (30-60 seconds)
I would first reproduce the Back navigation and use the browser Back forward cache diagnostics to list every exclusion reason. I would remove the unload handler, remove any unnecessary beforeunload handler, and move lifecycle cleanup to pagehide. I would pause timers, animation work, observers, and cross page connections there, then resume only the resources that need to run when pageshow fires. I would use event.persisted to detect a real cached restore, avoid repeating one time initialization, and verify the result with DevTools, the navigation entry type, the Network panel, and preserved page state.
When a shopper presses Back, the browser should be able to show the previous page immediately instead of loading the document again. Here, some page behavior stops the browser from keeping that page ready in memory. I would first find every blocking behavior. Then I would replace unsafe leaving page logic with browser lifecycle events that work with cached restoration. I would pause work while the page is stored, restart it when the page returns, and check that the cart, form values, scroll position, media state when relevant, and other visible state are still correct.
Useful Questions to Ask the Interviewer
Which browsers and commerce routes show the Back navigation reload?
What cross page resource stays open, such as a WebSocket or BroadcastChannel?
Which state must remain exactly as the shopper left it?
How to Explain It in an Interview
I would start with the user visible symptom: pressing Back reloads the document instead of restoring the previous page from the Back forward cache. My baseline is a repeatable navigation from the affected commerce page to another page and then Back, using the same browser, device class, production build, cache state, and steps each time. The success metric is a persisted restore with no new document request and with the page state still correct.
First, I would open the browser Back forward cache diagnostics and reproduce the navigation. I would record every reported exclusion reason instead of stopping after the first one. In this case, the known blockers are an unload handler and an open cross page communication resource. I would also review any other reasons that DevTools reports, such as an unnecessary beforeunload handler, a pending IndexedDB transaction, synchronous request work, or another resource that cannot remain active across the navigation.
Next, I would remove the unload listener. If a beforeunload listener is not required to protect unsaved user work, I would remove it too. Cleanup moves to pagehide. When pagehide runs, I stop polling timers, cancel pending animation work, disconnect observers when appropriate, and close WebSocket or BroadcastChannel resources that should not remain active while the page is stored. If analytics must be sent while leaving, I would use navigator.sendBeacon or another lifecycle safe request instead of relying on unload.
The pagehide event has a persisted flag. When it is true, the browser is preserving the page for a cached history restore. The DOM, JavaScript heap, scroll position, and normal in memory application state stay with that frozen page. I would not rebuild that state unnecessarily. I would save only volatile state that the application also needs as a fallback after a normal reload.
When pageshow fires, event.persisted being true tells me that this specific page display came from the Back forward cache. I would reconnect the socket or channel, restart polling, reconnect observers, and schedule visual work again. The restart functions must be idempotent so repeated Back and Forward navigation does not create duplicate timers, listeners, subscriptions, or connections. I would also skip one time initialization that must run only on a normal load.
For verification, I would repeat the same navigation after the change. DevTools should report that the page is eligible and show a restored Back forward cache navigation. In pageshow I would confirm event.persisted is true. I would also inspect performance.getEntriesByType("navigation")[0].type. A value of "back_forward" supports that this was a history navigation, but it is not enough by itself to prove a cached restore. The Network panel should show no new document request for the restored page, although a deliberately reopened socket or other resumed connection can create expected network activity.
Finally, I would verify correctness. The cart, form inputs, scroll position, focus behavior, media position when relevant, and other application state should match what the shopper left behind. I would confirm that only one timer, observer, socket, or channel is active after each restore. I would also test the normal reload path because pagehide and pageshow must work correctly when event.persisted is false. The final result is a page that is eligible for the Back forward cache, restores quickly on Back, and preserves correct application behavior.
Key Insight / Why This Solution Works
Reproduce the Back navigation on the affected commerce route with the same browser, device class, build, cache state, and steps.
Use the browser Back forward cache diagnostics to record every exclusion reason.
Remove the unload handler and remove an unnecessary beforeunload handler.
Replace leaving page cleanup with pagehide.
On pagehide, pause polling and timers, cancel pending animation work, disconnect observers, and close cross page connections that cannot remain active.
Let the browser preserve normal DOM and JavaScript memory state. Save only state that is also needed for a normal reload fallback.
On pageshow, use event.persisted to tell a cached restore from a normal page display.
Resume resources with idempotent restart functions and skip one time initialization on a cached restore.
Repeat the same navigation and confirm event.persisted is true, the navigation type is back_forward, DevTools reports a restore, and no new document request appears.
Verify cart state, form values, scroll position, focus behavior, media state when relevant, and that no timer, observer, socket, channel, listener, or subscription is duplicated.
Code
const appState = {
cart: { items: [] },
};
let pollingId = null;
let animationFrameId = null;
let socket = null;
let didInitialSetup = false;
// Observe only while the page is active.const observer = newResizeObserver(() => {
// Application specific resize work belongs here.
});
functionstartPolling() {
// Start only one polling timer after a normal load or cached restore.if (pollingId !== null) return;
pollingId = window.setInterval(() => {
// Application specific polling work belongs here.
}, 5000);
}
functionstopPolling() {
// Stop the active timer before the page is frozen or discarded.if (pollingId === null) return;
window.clearInterval(pollingId);
pollingId = null;
}
functionscheduleAnimation() {
// Schedule visual work only while the page is active.if (animationFrameId !== null) return;
animationFrameId = window.requestAnimationFrame(() => {
animationFrameId = null;
// Application specific visual update belongs here.
});
}
functioncancelAnimation() {
// Cancel pending visual work before leaving the active state.if (animationFrameId === null) return;
window.cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
functionopenSocket() {
// Avoid duplicate connections after repeated Back and Forward restores.if (
socket &&
(socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)
) {
return;
}
socket = newWebSocket('wss://example.com/cart');
}
functioncloseSocket() {
// Close the live connection before the page is stored or discarded.if (!socket) return;
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
socket.close(1000, 'pagehide');
}
socket = null;
}
functionsaveFallbackState() {
// Save only state that is also useful after a normal reload.sessionStorage.setItem('cart', JSON.stringify(appState.cart));
}
functionrestoreFallbackState() {
// Read optional fallback state without rebuilding all cached page state.const saved = sessionStorage.getItem('cart');
if (saved) {
appState.cart = JSON.parse(saved);
}
}
functionflushLeaveAnalytics() {
// sendBeacon is suitable for small fire and forget leave analytics.const payload = JSON.stringify({ event: 'pagehide' });
navigator.sendBeacon('/analytics', payload);
}
functionconnectActiveResources() {
// Resume only resources that should run while this page is active.startPolling();
openSocket();
observer.observe(document.documentElement);
scheduleAnimation();
}
functiondisconnectActiveResources() {
// Pause resources before the browser freezes or discards this page.stopPolling();
cancelAnimation();
observer.disconnect();
closeSocket();
}
functionrunInitialSetupOnce() {
// Keep one time initialization separate from cached restore work.if (didInitialSetup) return;
didInitialSetup = true;
// Register one time application behavior here.
}
window.addEventListener('pagehide', (event) => {
// pagehide replaces unload for lifecycle cleanup.disconnectActiveResources();
saveFallbackState();
flushLeaveAnalytics();
// This is useful diagnostic information while testing eligibility.console.log('pagehide persisted:', event.persisted);
});
window.addEventListener('pageshow', (event) => {
// A true persisted flag is the direct signal for a cached restore.if (event.persisted) {
restoreFallbackState();
connectActiveResources();
console.log('Back forward cache restore:', true);
return;
}
// A normal page display runs normal startup once.runInitialSetupOnce();
connectActiveResources();
});
functionreportNavigationType() {
// The navigation entry distinguishes history navigation from reload.const entry = performance.getEntriesByType('navigation')[0];
const type = entry ? entry.type : 'unknown';
console.log('Navigation type:', type);
console.log('History navigation:', type === 'back_forward');
}
reportNavigationType();
Why Interviewers Ask This
Interviewers ask this to see whether I understand browser page lifecycle behavior, can use browser diagnostics to find every Back forward cache blocker, and can change cleanup logic without losing page state. They also want to see whether I can separate a real cached restore from a normal history reload and verify that timers, connections, user interface state, and application data still behave correctly.
Common interview mistakes
A common mistake is fixing only the first blocker that DevTools reports and not checking again for additional exclusion reasons. Another is keeping unload or an unnecessary beforeunload handler even after moving some cleanup elsewhere. Developers can also reconnect timers, observers, sockets, channels, listeners, or subscriptions on every pageshow without guarding against duplicates. Another mistake is rebuilding the whole application even though a cached page already keeps its DOM and JavaScript memory. It is also wrong to treat the back_forward navigation type alone as proof of a cached restore. A pending IndexedDB transaction or synchronous request can remain a blocker, so those must be removed or completed before navigation rather than ignored.
Interview tip
Explain this as a browser lifecycle problem. Start with the Back navigation reload, show how DevTools identifies every blocker, move cleanup from unload to pagehide, resume only paused resources on pageshow, and finish by proving both the cached restore and correct page state.
Interviewer may ask next
What if performance.getEntriesByType("navigation")[0].type is "back_forward" but pageshow event.persisted is false?
That does not prove a Back forward cache restore. The back_forward value tells me that the commerce page was reached through browser history, but the browser may still have loaded a new document. For this workload I would use event.persisted in pageshow as the direct restore signal, then confirm the result with the browser Back forward cache diagnostics and the Network panel. A new document request would show that the page was loaded again. The tradeoff is that one signal is simple, but several independent checks give a safer conclusion.
How would you roll out the lifecycle change if reconnecting the WebSocket on pageshow could create duplicate subscriptions?
I would make the WebSocket and other restart functions idempotent so repeated Back and Forward restores do not create duplicate active resources. For this commerce page I would keep one socket reference, close it during pagehide, and reconnect only when there is no open or connecting socket. I would repeatedly navigate away and Back and verify that only one timer, one observer, and one socket are active after each restore. In production I would monitor connection counts, duplicated events, client errors, and Back forward cache eligibility while releasing the change gradually.
More questions load as you scroll
Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.