---
title: "INP vs FID: The New Interactivity Metric in Core Web Vitals"
description: "Interaction to Next Paint (INP) has replaced FID as a Core Web Vitals metric. Learn the differences, how to measure INP, and how to optimize page responsiveness."
date: 2025-12-30
updated: 2025-01-11
category: Optimization
tags: ["Core Web Vitals", "INP", "FID", "Performance", "SEO", "JavaScript"]
url: https://uper.pl/en/blog/inp-vs-fid/
---

# INP vs FID: The New Interactivity Metric in Core Web Vitals

In March 2024, Google officially replaced **First Input Delay (FID)** with the new **Interaction to Next Paint (INP)** metric as part of [Core Web Vitals](/en/blog/core-web-vitals/). INP better reflects actual page responsiveness and is more challenging to optimize. This guide explains the differences and shows how to improve your scores.

## What Was FID?

**First Input Delay** measured the time from the user's first interaction (click, tap) until the browser could start handling that event. It only measured the **first** interaction.

### FID Limitations

1. **Only first interaction** - ignored all subsequent ones
2. **Only delay** - didn't measure processing time
3. **Easy to optimize** - fast initial load was enough
4. **Didn't reflect reality** - page could be slow after first interaction

## What is INP?

**Interaction to Next Paint** measures page responsiveness throughout the entire time of use. It considers all interactions (clicks, taps, key presses) and reports the worst one (technically: the 98th percentile).

### What Does INP Measure?

INP measures the time from interaction to the moment the next frame is rendered:

```
INP = Input Delay + Processing Time + Presentation Delay
```

1. **Input Delay** - time waiting to start processing (like FID)
2. **Processing Time** - time executing event handlers
3. **Presentation Delay** - time rendering DOM changes

### INP Thresholds

| Score | Rating |
|-------|--------|
| ≤ 200ms | **Good** (green) |
| 200ms - 500ms | **Needs Improvement** (orange) |
| > 500ms | **Poor** (red) |

## FID vs INP Comparison

| Aspect | FID | INP |
|--------|-----|-----|
| What it measures | Only delay | Delay + processing + rendering |
| Which interactions | Only first | All (reports worst) |
| Thresholds | ≤100ms good | ≤200ms good |
| Optimization difficulty | Low | High |
| Status | Deprecated (March 2024) | Current Core Web Vital |

## How to Measure INP?

### Lab Data

**Lighthouse (Chrome DevTools):**
- INP is not directly measured in Lighthouse
- Use **Total Blocking Time (TBT)** as a proxy

**Chrome DevTools - Performance:**
1. Open Performance panel
2. Record while interacting with the page
3. Look for long tasks in Main thread

### Field Data (Real User Data)

**web-vitals library:**

```javascript

onINP((metric) => {
  console.log('INP:', metric.value);
  console.log('Rating:', metric.rating); // good, needs-improvement, poor
  console.log('Entries:', metric.entries); // Interaction details
});
```

**Google Search Console:**
- Core Web Vitals report shows INP from CrUX data

**PageSpeed Insights:**
- "Diagnose performance issues" section shows INP

### Chrome DevTools - Web Vitals Overlay

1. Open DevTools
2. Press **Ctrl+Shift+P**
3. Type "Show Web Vitals"
4. Interacting with the page will show INP in real-time

## Causes of Poor INP

### 1. Long JavaScript Tasks

JavaScript blocks the main thread. Any task >50ms is a "long task" affecting INP.

```javascript
// Bad - long synchronous task
function processData(items) {
  items.forEach(item => {
    // Heavy computations...
    heavyComputation(item);
  });
}

// Good - split into smaller tasks
async function processDataAsync(items) {
  for (const item of items) {
    heavyComputation(item);
    // Yield control to the browser
    await scheduler.yield();
  }
}

// Alternative with setTimeout
function processDataChunked(items, chunkSize = 10) {
  let index = 0;

  function processChunk() {
    const chunk = items.slice(index, index + chunkSize);
    chunk.forEach(heavyComputation);
    index += chunkSize;

    if (index < items.length) {
      setTimeout(processChunk, 0);
    }
  }

  processChunk();
}
```

### 2. Heavy Event Handlers

```javascript
// Bad - heavy handler blocks rendering
button.addEventListener('click', () => {
  // 500ms of computations...
  processLargeDataset();
  updateUI();
});

// Good - separate computations from UI
button.addEventListener('click', async () => {
  // Immediately show feedback
  button.disabled = true;
  showSpinner();

  // Computations in next task
  await scheduler.yield();
  const result = processLargeDataset();

  // Update UI
  await scheduler.yield();
  updateUI(result);
  hideSpinner();
  button.disabled = false;
});
```

### 3. Too Many Re-renders

```javascript
// Bad - each change triggers reflow
items.forEach(item => {
  const div = document.createElement('div');
  div.textContent = item.name;
  container.appendChild(div); // Reflow!
});

// Good - batch DOM operations
const fragment = document.createDocumentFragment();
items.forEach(item => {
  const div = document.createElement('div');
  div.textContent = item.name;
  fragment.appendChild(div);
});
container.appendChild(fragment); // Single reflow
```

### 4. Layout Thrashing

```javascript
// Bad - multiple reads and writes
elements.forEach(el => {
  const height = el.offsetHeight; // Read (force layout)
  el.style.height = height + 10 + 'px'; // Write (invalidate layout)
});

// Good - read first, then write
const heights = elements.map(el => el.offsetHeight);
elements.forEach((el, i) => {
  el.style.height = heights[i] + 10 + 'px';
});
```

### 5. Third-party Scripts

Third-party scripts (analytics, ads, chat widgets) often have heavy handlers.

## INP Optimization Techniques

### 1. Scheduler API (scheduler.yield)

New API allowing you to yield control to the browser:

```javascript
async function handleClick() {
  // Step 1
  await scheduler.yield();

  // Step 2
  await scheduler.yield();

  // Step 3
}

// Polyfill for older browsers
if (!('scheduler' in window)) {
  window.scheduler = {
    yield: () => new Promise(resolve => setTimeout(resolve, 0))
  };
}
```

### 2. requestIdleCallback

Execute less urgent tasks when the browser is idle:

```javascript
// Background processing
function processInBackground(items) {
  let index = 0;

  function processNext(deadline) {
    while (index < items.length && deadline.timeRemaining() > 0) {
      processItem(items[index]);
      index++;
    }

    if (index < items.length) {
      requestIdleCallback(processNext);
    }
  }

  requestIdleCallback(processNext);
}
```

### 3. requestAnimationFrame

Synchronize visual changes with frame rate:

```javascript
// Bad - may hit mid-frame
button.addEventListener('click', () => {
  element.style.transform = 'translateX(100px)';
});

// Good - synchronized with frame
button.addEventListener('click', () => {
  requestAnimationFrame(() => {
    element.style.transform = 'translateX(100px)';
  });
});
```

### 4. Web Workers

Move heavy computations off the main thread:

```javascript
// main.js
const worker = new Worker('worker.js');

button.addEventListener('click', () => {
  showSpinner();
  worker.postMessage({ data: largeDataset });
});

worker.onmessage = (e) => {
  hideSpinner();
  displayResults(e.data);
};

// worker.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};
```

### 5. Debouncing and Throttling

```javascript
// Debounce - execute after sequence ends
function debounce(fn, delay) {
  let timeout;
  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn(...args), delay);
  };
}

// Throttle - execute at most once per X ms
function throttle(fn, limit) {
  let inThrottle;
  return (...args) => {
    if (!inThrottle) {
      fn(...args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

// Usage
input.addEventListener('input', debounce(handleSearch, 300));
window.addEventListener('scroll', throttle(handleScroll, 100));
```

### 6. Passive Event Listeners

```javascript
// Bad - may block scrolling
element.addEventListener('touchstart', handler);

// Good - declares that preventDefault won't be called
element.addEventListener('touchstart', handler, { passive: true });
```

### 7. CSS contain

Limit the area that needs to be recalculated:

```css
.widget {
  contain: content; /* Isolates layout and paint */
}

.modal {
  contain: strict; /* Full isolation */
}
```

### 8. content-visibility

Defer rendering of elements outside the viewport:

```css
.card {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px; /* Estimated height */
}
```

## Framework Optimization

### React

```jsx
// Use startTransition for less urgent updates

function handleClick() {
  // Urgent update - immediate
  setIsLoading(true);

  // Less urgent - can be deferred
  startTransition(() => {
    setResults(computeExpensiveResults());
  });
}

// useDeferredValue for expensive renders

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);

  // Render with deferred query
  return <Results query={deferredQuery} />;
}

// memo to avoid unnecessary re-renders
const ExpensiveComponent = React.memo(({ data }) => {
  // ...
});
```

### Vue

```vue
<script setup>

// shallowRef for large objects
const largeData = shallowRef(null);

// Lazy computed
const expensiveComputed = computed(() => {
  // Calculations only when needed
});
</script>

<template>
  <!-- v-once for static content -->
  <div v-once>{{ staticContent }}</div>

  <!-- v-memo for caching -->
  <div v-for="item in list" :key="item.id" v-memo="[item.id]">
    {{ item.name }}
  </div>
</template>
```

## INP Optimization Checklist

### JavaScript
- [ ] Split long tasks into smaller ones (under 50ms)
- [ ] Use scheduler.yield() or setTimeout(0)
- [ ] Move heavy computations to Web Workers
- [ ] Debounce/throttle event handlers

### DOM
- [ ] Batch DOM operations (DocumentFragment)
- [ ] Avoid layout thrashing
- [ ] Use requestAnimationFrame for animations
- [ ] Apply CSS contain for isolation

### Third-party
- [ ] Defer non-essential scripts
- [ ] Lazy-load widgets (chat, social)
- [ ] Consider [server-side GTM](/en/blog/gtm-server-side-vs-client-side/)

### Framework
- [ ] React: startTransition, useDeferredValue
- [ ] Vue: v-once, v-memo, shallowRef
- [ ] Virtualization for long lists

## Monitoring INP

### Real User Monitoring (RUM)

```javascript

onINP((metric) => {
  // Send to analytics
  gtag('event', 'web_vitals', {
    metric_name: metric.name,
    metric_value: metric.value,
    metric_rating: metric.rating,
    metric_delta: metric.delta
  });

  // Or to your own endpoint
  navigator.sendBeacon('/analytics', JSON.stringify({
    name: 'INP',
    value: metric.value,
    page: location.pathname
  }));
});
```

### Alerts for Poor INP

Set up alerts in Google Search Console or your own monitoring system when INP exceeds the threshold.

## Summary

**INP** is a more challenging metric than FID, but it better reflects actual user experience. Key points:

1. **INP measures the entire interaction** - not just delay
2. **Reports the worst interaction** - you need to optimize everything
3. **Split long tasks** - no task should take >50ms
4. **Yield control to the browser** - scheduler.yield(), requestIdleCallback
5. **Web Workers** - for heavy computations
6. **Monitor with RUM** - lab data isn't enough

The goal is **INP below 200ms** for 75% of users. To understand how INP fits alongside other ranking factors like rendering, structured data, and E-E-A-T, check out our [ultimate guide to web technologies and SEO](/en/blog/web-technologies-seo-google-rankings/).

## Sources

1. **web.dev - Interaction to Next Paint (INP)**
[https://web.dev/articles/inp](https://web.dev/articles/inp)

2. **web.dev - Optimize INP**
[https://web.dev/articles/optimize-inp](https://web.dev/articles/optimize-inp)

3. **Chrome Developers - INP announcement**
[https://developer.chrome.com/blog/inp-cwv](https://developer.chrome.com/blog/inp-cwv)

4. **web.dev - Long tasks**
[https://web.dev/articles/optimize-long-tasks](https://web.dev/articles/optimize-long-tasks)

5. **MDN - Scheduler API**
[https://developer.mozilla.org/en-US/docs/Web/API/Scheduler](https://developer.mozilla.org/en-US/docs/Web/API/Scheduler)

6. **web.dev - First Input Delay (FID) - archived**
[https://web.dev/articles/fid](https://web.dev/articles/fid)
