Prompt and context
Users report a janky page after clicking a filter button. Explain how to locate main-thread long tasks, assess their effect on Interaction to Next Paint (INP), and verify a fix.
Distinguish input delay, event-handler work, and the next paint. “Add debounce” or “use a worker” is not a diagnosis. Cover real-user evidence, lab traces, regression metrics, and fallback boundaries.
What the interviewer is testing
Performance model
A strong answer breaks an interaction into input waiting, event processing, rendering, and painting, then explains why a busy main thread delays browser feedback.
Evidence-based diagnosis
Use PerformanceObserver, browser Performance traces, and interaction context to identify the script, component, and data size instead of guessing.
Repair tradeoffs
Discuss task splitting, less synchronous work, virtualization, deferred non-critical updates, and workers while accounting for serialization and state consistency.
Real-user validation
Compare p75 INP, long-task counts, interaction percentiles, and business conversion. Separate lab samples from real devices, networks, and low-end CPUs.
Clarifying questions to ask
- Does jank affect every interaction or one filter condition?
- Which devices, browsers, and data sizes are in scope?
- Is the issue initial load, event processing, or post-event layout and paint?
- Do real-user INP, event duration, and long-task samples exist?
- Must results update synchronously, or can the UI respond before computation finishes?
- May sorting, pagination, or result precision change?
30-second answer framework
“I would use real-user INP and interaction samples to find the affected page, then record a Performance trace with the same device and data size. PerformanceObserver can collect long tasks over 50ms; the trace locates the call stack and paint phase. If synchronous event work is too large, I would split work, reduce rerenders, and virtualize the list. I would consider a worker only for profitable pure CPU work, including message and serialization cost. Afterward I would compare p75 INP, interaction long tasks, and conversion.”
Step-by-step deep dive
Step 1: Establish a baseline
Segment p75 INP, event duration, next-paint delay, long-task count, and errors by page, interaction, device, and release. A fast local machine is not a population baseline.
Step 2: Reproduce and locate
Record the filter interaction in the browser Performance panel. Inspect the main-thread flame chart, long tasks, layout, and paint. A PerformanceObserver can collect runtime long-task entries and attach page, interaction, and release context.
Step 3: Separate bottlenecks
Long input wait points to a preceding synchronous task; long event work points to parsing, filtering, or state updates; long paint points to layout, styles, or a large DOM. INP is the full input-to-next-paint path, not one function duration.
Step 4: Reduce synchronous work
Reduce computation and rerenders with pagination, virtualization, incremental filtering, and caching. Move logging, prefetching, and analytics off the critical interaction path. Use idle or chunked scheduling only with a plan for insufficient idle time.
Step 5: Evaluate a worker
A worker fits pure CPU work with transferable or manageable data. Large copies, frequent messages, and DOM access can erase gains. Version results, cancel stale work, and prevent an old filter from overwriting newer state.
Step 6: Verify and prevent regressions
Replay on representative low-end devices and datasets, then roll out gradually. Compare p75/p95 INP, long-task count, interaction completion, cancellations, and conversion. Set a budget that alerts or blocks regressions.
Model high-quality answer
“I would first use real-user data to identify the interaction, device, and release where the problem clusters, then record the filter with the same data size. The trace shows array filtering, state updates, and list layout in one handler, so I would cache filtering, virtualize the list, and defer non-critical work.
I would collect long tasks over 50ms with PerformanceObserver and correlate them to the page and interaction. If filtering remains a pure CPU bottleneck, I would move it to a worker, cap message size, and discard stale results. I would canary the fix on low-end devices and compare p75 INP, long-task count, cancellation, and filter completion before expanding.”
Common mistakes
- Looking only at average latency → tail users disappear → segment p75/p95 INP by device.
- Treating a long task as INP → input and paint are omitted → trace the full interaction timeline.
- Adding debounce to any jank → necessary feedback may be delayed → locate compute, render, and network bottlenecks first.
- Moving all work to a worker → copy and messaging cost grows → migrate only profitable pure CPU work.
- Testing only on a development laptop → low-end devices still jank → replay representative devices, data, and rollout cohorts.
- Chunking without cancellation → stale results overwrite new state → add version, cancellation, and commit checks.
- Using only lab Lighthouse → real interactions are missed → sample real-user INP and long tasks.
- No performance budget after the fix → regressions go unnoticed → set thresholds and continuous monitoring.
Follow-up questions and responses
Follow-up 1: A long task is only 60ms. Why is INP still poor?
Inspect input wait, layout and paint after the handler, and neighboring tasks. INP is the complete response path; one 60ms task is not the whole explanation.
Follow-up 2: What if a worker makes the result slower?
Measure serialization, transfer, and scheduling. Keep a main-thread fast path for small data, batch larger messages or transfer buffers, and cancel stale requests.
Follow-up 3: How do you observe long-task sources?
Record start time, duration, page, and release with PerformanceObserver, then use the Performance trace for the call stack. Never log sensitive user input.
Follow-up 4: What if filtering must feel immediate?
Acknowledge the input and show progress synchronously, then compute incrementally. Keep result versions aligned with filter state and expose a temporary versus completed state.
Follow-up 5: How do you prove conversion was not harmed?
Canary the change and compare p75 INP, completion, cancellation, errors, and core conversion by device and network, with explicit stop conditions.
Source 1: MDN performance data
MDN defines long-task records as tasks lasting 50ms or more, providing runtime evidence of main-thread blocking.
Source 2: PerformanceObserver
MDN documents observing performance entries with PerformanceObserver, supporting runtime collection of long tasks and release context.
Source 3: web.dev Long Tasks and INP
web.dev explains that long tasks block the main thread and delay feedback, and recommends splitting work, reducing synchronous work, and evaluating workers; INP captures interaction-to-next-paint responsiveness.