Interaction to Next Paint (INP) is the newest Core Web Vital metric that replaced First Input Delay (FID) in March 2024. While FID only measured the first interaction delay, INP provides a more comprehensive view of your page's responsiveness throughout the entire user session.
In this complete guide, we'll explain what INP is, why Google made the switch from FID, and most importantly, how to optimize your website for excellent INP scores.
What is Interaction to Next Paint (INP)?
Interaction to Next Paint (INP) is a performance metric that measures the responsiveness of a web page to user interactions. It assesses how quickly a page responds to all user interactions—such as clicks, taps, and keyboard inputs—throughout the entire visit.
How INP Works
INP observes the latency of all user interactions during a page visit and reports a single value that represents the page's overall responsiveness. It measures three phases:
- Input delay: Time from when the user initiates the interaction to when event handlers begin running
 - Processing time: Time spent executing event handlers
 - Presentation delay: Time from when handlers finish to when the browser paints the next frame
 
INP = Input Delay + Processing Time + Presentation Delay
The metric focuses on the interaction with the longest delay (excluding outliers) to represent the worst responsiveness users typically experience.
INP vs. FID: What Changed?
Why Google Replaced FID with INP
First Input Delay (FID) had significant limitations:
- Only measured the first interaction
 - Didn't measure processing time or presentation delay
 - Missed slow interactions that happened later in the page visit
 - Didn't capture keyboard interactions
 - Could show good scores even if the page was generally unresponsive
 
INP addresses all these issues by:
- Measuring all interactions throughout the page visit
 - Capturing the complete interaction latency (not just input delay)
 - Providing a more accurate picture of real-world responsiveness
 - Including all types of interactions (clicks, taps, keyboard)
 
Migration Timeline
- May 2022: INP introduced as an experimental metric
 - May 2023: Google announced INP would replace FID
 - March 2024: INP officially became a Core Web Vital
 - September 2024: FID deprecated entirely
 
INP Thresholds: Good, Needs Improvement, and Poor
Google defines three INP performance levels:
- Good: 200 milliseconds or less ✅
 - Needs Improvement: Between 200 and 500 milliseconds ⚠️
 - Poor: More than 500 milliseconds ❌
 
Your goal should be to achieve "Good" INP (≤200ms) for at least 75% of your page visits.
Why 200ms?
Research shows that users perceive interactions under 200ms as instantaneous. Beyond this threshold, delays become noticeable and can negatively impact user experience and satisfaction.
Why INP Matters
User Experience Impact
Slow INP creates a frustrating, unresponsive experience:
- Buttons that don't respond immediately to clicks
 - Forms that feel sluggish
 - Navigation that lags
 - Interactions that feel "broken"
 
Key Statistics:
- Pages with good INP have 30% lower bounce rates
 - Users are 70% more likely to complete actions on responsive pages
 - Poor INP correlates with decreased user satisfaction scores
 
SEO Impact
As a Core Web Vital, INP is a ranking factor in Google's algorithm. While not the most important factor, poor INP can hurt your rankings, especially when:
- Competing with similar content
 - Targeting competitive keywords
 - Fighting for top positions
 
Google uses real user data (CrUX) to evaluate INP, so your actual users' experiences directly impact your search visibility.
What Counts as an Interaction?
INP measures specific types of user interactions:
Included Interactions
- Mouse clicks: Clicks on buttons, links, and clickable elements
 - Taps on touchscreens: All tap interactions on mobile devices
 - Keyboard presses: Any keyboard input that triggers functionality
 
Not Included
- Hover events: Mouse hover doesn't count as interaction
 - Scrolling: Scroll events are not measured by INP
 - Zooming and pinching: These gestures are excluded
 - CSS transitions/animations: Pure visual changes without user input
 
Common INP Issues and Root Causes
Understanding why your INP is poor helps you fix it effectively.
1. Long JavaScript Tasks
JavaScript that runs for extended periods blocks the main thread, preventing the browser from responding to interactions.
Common culprits:
- Large frameworks without code splitting
 - Heavy data processing on the main thread
 - Unoptimized loops and computations
 - Synchronous API calls
 
How to identify:
- Use Chrome DevTools Performance panel
 - Look for long tasks (>50ms) in red
 - Check the "Bottom-Up" tab for expensive functions
 
2. Large JavaScript Bundles
Downloading, parsing, and executing large JavaScript files delays interactivity.
Signs of this issue:
- Bundle sizes over 300-500KB
 - Many third-party scripts
 - Unused code in bundles
 - No code splitting implemented
 
3. Heavy Event Handlers
Event handlers that perform complex operations delay the browser's response.
Examples:
// BAD: Heavy synchronous processing
button.addEventListener('click', () => {
  // Expensive DOM manipulation
  for (let i = 0; i < 10000; i++) {
    document.querySelector('.item').style.color = 'red'
  }
})
// GOOD: Optimized and async
button.addEventListener('click', async () => {
  requestAnimationFrame(() => {
    const items = document.querySelectorAll('.item')
    items.forEach(item => item.classList.add('highlighted'))
  })
})
4. Third-Party Scripts
External scripts (analytics, ads, social widgets) can significantly impact INP.
Problem scripts:
- Real-time chat widgets
 - Social media embeds
 - Ad networks
 - Tag managers with many tags
 - A/B testing frameworks
 
5. Main Thread Blocking
When the main thread is busy, it can't respond to user interactions.
Causes:
- Rendering large DOM trees
 - Complex CSS calculations
 - Layout thrashing (forced synchronous layouts)
 - Long-running synchronous operations
 
9 Proven Techniques to Optimize INP
Let's dive into actionable strategies to improve your INP score.
1. Break Up Long Tasks
Split long-running JavaScript into smaller chunks to allow the browser to handle interactions.
Strategy: Use setTimeout or scheduler.yield()
// BAD: Long blocking task
function processItems(items) {
  items.forEach(item => {
    // Heavy processing
    processItem(item)
  })
}
// GOOD: Break into chunks
async function processItems(items) {
  for (let i = 0; i < items.length; i++) {
    processItem(items[i])
    // Yield to main thread every 50 items
    if (i % 50 === 0) {
      await new Promise(resolve => setTimeout(resolve, 0))
    }
  }
}
Target: Keep tasks under 50ms
2. Optimize Event Handlers
Make event handlers as lightweight as possible.
Best practices:
// Use event delegation
document.addEventListener('click', (e) => {
  if (e.target.matches('.button')) {
    handleClick(e.target)
  }
})
// Debounce expensive operations
const debouncedHandler = debounce(() => {
  // Expensive operation
}, 300)
input.addEventListener('input', debouncedHandler)
// Use passive event listeners
element.addEventListener('touchstart', handler, { passive: true })
3. Defer Non-Critical JavaScript
Load only essential JavaScript initially; defer everything else.
Implementation:
<!-- Critical JavaScript -->
<script src="critical.js"></script>
<!-- Defer non-critical scripts -->
<script src="analytics.js" defer></script>
<script src="chat-widget.js" defer></script>
<!-- Or load on interaction -->
<script>
  document.getElementById('chat-button').addEventListener('click', () => {
    import('./chat-module.js').then(module => {
      module.initChat()
    })
  }, { once: true })
</script>
4. Use Code Splitting
Break large JavaScript bundles into smaller chunks that load on demand.
Webpack/Vite example:
// Instead of importing everything upfront
import HeavyFeature from './heavy-feature'
// Use dynamic imports
button.addEventListener('click', async () => {
  const { HeavyFeature } = await import('./heavy-feature')
  HeavyFeature.init()
})
Expected impact: 30-50% reduction in initial JavaScript
5. Implement Web Workers
Move heavy computations off the main thread using Web Workers.
Example:
// Main thread
const worker = new Worker('worker.js')
button.addEventListener('click', () => {
  worker.postMessage({ data: largeDataset })
  worker.onmessage = (e) => {
    updateUI(e.data) // Fast UI update
  }
})
// worker.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data)
  self.postMessage(result)
}
Use cases:
- Data processing
 - Image manipulation
 - Complex calculations
 - JSON parsing of large datasets
 
6. Optimize Third-Party Scripts
Audit and optimize external scripts that impact responsiveness.
Strategies:
<!-- Load third-parties async -->
<script src="https://analytics.com/script.js" async></script>
<!-- Use facades for heavy embeds -->
<div class="youtube-facade" data-video-id="abc123">
  <button onclick="loadVideo(this)">Play Video</button>
</div>
<!-- Preconnect to third-party origins -->
<link rel="preconnect" href="https://cdn.thirdparty.com">
Action items:
- Remove unused third-party scripts
 - Load scripts only when needed (on interaction)
 - Consider self-hosting critical third-parties
 - Use lighter alternatives when available
 
7. Reduce JavaScript Execution Time
Optimize your code to run faster.
Techniques:
// Use efficient data structures
const lookup = new Map() // Faster than object for lookups
const uniqueItems = new Set() // Faster than array for uniqueness
// Minimize DOM queries
// BAD
for (let i = 0; i < 100; i++) {
  document.querySelector('.item').textContent = i
}
// GOOD
const item = document.querySelector('.item')
for (let i = 0; i < 100; i++) {
  item.textContent = i
}
// Use requestAnimationFrame for visual updates
function updateUI() {
  requestAnimationFrame(() => {
    // DOM manipulations here
  })
}
8. Optimize Rendering Performance
Prevent layout thrashing and unnecessary repaints.
Best practices:
// BAD: Causes layout thrashing
elements.forEach(el => {
  el.style.width = el.offsetWidth + 10 + 'px' // Read then write
})
// GOOD: Batch reads and writes
const widths = elements.map(el => el.offsetWidth) // Batch reads
elements.forEach((el, i) => {
  el.style.width = widths[i] + 10 + 'px' // Batch writes
})
// Use CSS transforms instead of layout properties
// BAD: Triggers layout
element.style.left = '100px'
// GOOD: Uses compositor
element.style.transform = 'translateX(100px)'
9. Monitor and Test Regularly
Set up continuous monitoring to catch INP regressions.
Tools:
- SpeedCheck Pro: Quick INP analysis with recommendations
 - Chrome DevTools: Use the Performance panel to analyze interactions
 - Lighthouse: Regular audits in CI/CD
 - Real User Monitoring (RUM): Track actual user INP data
 - Web Vitals Extension: Monitor INP on any page
 
Testing Your INP
Lab Testing (Synthetic)
Chrome DevTools:
- Open DevTools → Performance
 - Start recording
 - Perform interactions
 - Stop recording
 - Analyze interaction timing in the timeline
 
PageSpeed Insights:
- Provides both lab and field data
 - Shows INP score and specific issues
 - Offers optimization suggestions
 
Real User Monitoring (RUM)
Google Search Console:
- Core Web Vitals report shows real INP data
 - Groups pages by INP performance
 - Shows trend over time
 
Web Vitals JavaScript Library:
import { onINP } from 'web-vitals'
onINP((metric) => {
  // Send INP data to analytics
  analytics.send({
    name: 'INP',
    value: metric.value,
    rating: metric.rating // 'good', 'needs-improvement', or 'poor'
  })
})
Debugging Poor INP
When you have poor INP, follow this debugging process:
Step 1: Identify the Slow Interaction
Use Chrome DevTools to record user interactions and identify which specific interactions are slow.
Step 2: Analyze the Timeline
Look at what's happening during the interaction:
- Is there a long task before the interaction?
 - Are event handlers taking too long?
 - Is there heavy rendering work?
 
Step 3: Profile JavaScript Execution
Use the Performance panel's "Bottom-Up" view to find expensive functions.
Step 4: Check Third-Party Scripts
Temporarily disable third-party scripts to see if they're the culprit.
Step 5: Test Fixes
Make changes and re-test to verify improvements.
Real-World INP Optimization Examples
Case Study 1: E-commerce Checkout
- Before: INP = 850ms (button clicks felt sluggish)
 - Issues: Heavy form validation, large bundle, third-party payment scripts
 - Changes: Code splitting, moved validation to web worker, lazy loaded payment scripts
 - After: INP = 180ms
 - Result: 18% increase in checkout completion rate
 
Case Study 2: News Website
- Before: INP = 650ms (article interactions delayed)
 - Issues: Heavy analytics, ad scripts, infinite scroll implementation
 - Changes: Deferred analytics, optimized ad loading, improved scroll handler
 - After: INP = 195ms
 - Result: 25% increase in page views per session
 
Case Study 3: SaaS Dashboard
- Before: INP = 720ms (dropdown menus and filters unresponsive)
 - Issues: Large React bundle, unoptimized state updates, heavy rendering
 - Changes: Implemented React.memo, virtualized lists, reduced bundle by 45%
 - After: INP = 165ms
 - Result: 31% improvement in user satisfaction scores
 
INP Optimization Checklist
Use this checklist to systematically improve your INP:
Audit Phase
- Measure current INP with Chrome DevTools
 - Check INP in Google Search Console
 - Test on mobile devices (slower processors)
 - Identify slowest interactions
 - Profile JavaScript execution
 
Optimization Phase
- Break long tasks into chunks (<50ms each)
 - Implement code splitting for large bundles
 - Defer or lazy load third-party scripts
 - Optimize event handlers
 - Move heavy computations to web workers
 - Reduce JavaScript bundle sizes
 - Minimize DOM manipulations in event handlers
 - Use passive event listeners where appropriate
 
Monitoring Phase
- Set up real user monitoring (RUM)
 - Add INP tracking to analytics
 - Create alerts for INP regressions
 - Test INP regularly in CI/CD pipeline
 - Review INP trends monthly
 
Frequently Asked Questions About INP
Q: What's the difference between INP and FID?
A: FID only measured the first interaction on a page, while INP measures all interactions throughout the page visit. INP provides a much more accurate picture of overall page responsiveness.
Q: Can hover events affect INP?
A: No, INP only measures discrete interactions like clicks, taps, and keyboard presses. Hover events are not included in INP calculations.
Q: How is INP calculated if there are many interactions?
A: INP reports the longest interaction duration (excluding outliers). For pages with fewer than 50 interactions, it's the worst interaction. For pages with more, it uses the 98th percentile.
Q: Does INP affect mobile rankings more than desktop?
A: Google uses mobile performance data for rankings (mobile-first indexing), so mobile INP is what counts. Mobile devices typically have slower processors, making INP optimization even more critical.
Q: Can good INP scores compensate for poor LCP?
A: No, all three Core Web Vitals (LCP, INP, CLS) are evaluated independently. You need good scores across all three metrics for optimal SEO and user experience.
Conclusion
INP is the most comprehensive Core Web Vital for measuring interactivity and responsiveness. By optimizing INP, you create a snappier, more responsive experience that users love and that Google rewards in search rankings.
Key takeaways:
- Aim for INP under 200ms for all interactions
 - Break up long JavaScript tasks into smaller chunks
 - Optimize event handlers and minimize main thread blocking
 - Use code splitting and lazy loading
 - Monitor real user INP data continuously
 
Ready to optimize your INP? Test your website with SpeedCheck Pro to get instant analysis and personalized recommendations for improving responsiveness.
Additional Resources
Related Articles
Test Your Website's Performance
Ready to see how your website measures up? Get instant Core Web Vitals analysis and actionable recommendations.