JavaScript Impact on INP — Main Thread Optimization
Understand how JavaScript execution affects Interaction to Next Paint (INP). Learn about long tasks, main thread blocking, event handler optimization, and yielding strategies.
Detailed Explanation
How JavaScript Affects INP
JavaScript is the primary cause of poor INP scores. Every millisecond the main thread spends executing JavaScript is a millisecond it cannot respond to user input.
The Main Thread Bottleneck
The browser's main thread handles:
- JavaScript execution
- Style calculation
- Layout
- Paint
- User input event handling
All of these share a single thread. When a long JavaScript task is running, the browser cannot process a click or keypress until the task completes.
Long Tasks and INP
A "long task" is any task exceeding 50ms. During a long task, the browser is completely unresponsive to user input. If a user clicks a button during a 300ms long task, they will wait up to 300ms before anything happens.
Timeline:
[=== 300ms JS task ===]
↑ user clicks here
|←── 200ms wait ──→| event handler runs | paint
↑ user sees result
Measuring JavaScript Impact
Use the Performance panel in Chrome DevTools:
- Record a session that includes user interactions
- Look for long tasks (red flagged bars) in the Main thread
- Click on tasks to see the call stack
- Correlate with interaction markers to find INP-affecting tasks
The PerformanceObserver API can measure in production:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'event') {
const delay = entry.processingStart - entry.startTime;
const processing = entry.processingEnd - entry.processingStart;
const presentation = entry.startTime + entry.duration - entry.processingEnd;
console.log({
name: entry.name,
inputDelay: delay,
processingTime: processing,
presentationDelay: presentation,
totalDuration: entry.duration,
});
}
}
});
observer.observe({ type: 'event', buffered: true, durationThreshold: 16 });
Optimization Patterns
Pattern 1: Yield Between Work
// Use scheduler.yield() for cooperative scheduling
async function handleFilter(criteria) {
const data = getFilteredData(criteria); // fast
await scheduler.yield();
renderResults(data); // might be slow
await scheduler.yield();
updateAnalytics(criteria); // non-critical
}
Pattern 2: Move Work Off Main Thread
// Use a Web Worker for heavy computation
const worker = new Worker('/sort-worker.js');
worker.postMessage({ data: largeArray, sortKey: 'name' });
worker.onmessage = (e) => renderSorted(e.data);
Pattern 3: Optimize React Re-Renders
// Use React.memo and useMemo to prevent unnecessary work
const ExpensiveList = React.memo(({ items }) => {
const sorted = useMemo(() => sortItems(items), [items]);
return sorted.map(item => <Item key={item.id} {...item} />);
});
Use Case
JavaScript optimization for INP is critical for modern web applications. React, Vue, and Angular apps often have complex component trees that re-render on interaction. Dashboard applications with charts, tables, and filters are common INP offenders. Any page where third-party scripts (analytics, ads, chat) compete with user interactions for main thread time needs INP optimization.
Try It — Web Vitals Reference
Related Topics
INP Explained — Interaction to Next Paint Deep Dive
Core Web Vitals
TBT Reduction — Total Blocking Time Optimization Guide
Supplementary Metrics
Third-Party Scripts and Web Vitals — Impact and Mitigation
Optimization
LCP Optimization Strategies — Largest Contentful Paint Guide
Core Web Vitals
Performance Monitoring Tools for Web Vitals
Tools & Workflow