Learn the concept
Runtime Performance & Profiling
Long tasks are JavaScript executions exceeding 50ms that block the main thread, causing unresponsive UI. Identify them with the Long Tasks API and PerformanceObserver, then optimize by breaking work into smaller chunks, yielding to the main thread, and offloading CPU-intensive work to Web Workers.
The browser's main thread handles JavaScript execution, layout, painting, and user input processing. When a single JavaScript task runs for more than 50ms, it's classified as a long task. During a long task, the browser cannot respond to user input (clicks, typing, scrolling), causing the UI to feel frozen.
To maintain a responsive UI, the browser needs to process a frame roughly every 16ms (60fps). Research shows users perceive delays above 100ms as sluggish. Keeping tasks under 50ms leaves headroom for rendering and input processing within that 100ms budget.
1. Long Tasks API — Observe long tasks programmatically:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Long task detected:', {
duration: entry.duration,
startTime: entry.startTime,
name: entry.name,
});
}
});
observer.observe({ type: 'longtask', buffered: true });2. Chrome DevTools Performance panel — Record a trace and look for red triangles on the main thread timeline. Each marks a long task. Click to see the call stack.
3. Lighthouse / PageSpeed Insights — Reports Total Blocking Time (TBT), which sums all long task durations beyond 50ms. This correlates strongly with INP (Interaction to Next Paint).
INP (Interaction to Next Paint) is a Core Web Vital measuring responsiveness. When a user interaction triggers JavaScript that becomes a long task, the time from interaction to visual feedback (paint) is delayed. Reducing long tasks directly improves INP.
1. Break work into chunks — Split large loops or data processing into smaller batches:
async function processItems(items) {
for (let i = 0; i < items.length; i += 100) {
const chunk = items.slice(i, i + 100);
processChunk(chunk);
await yieldToMain(); // Give browser a chance to handle input
}
}2. Yield to the main thread — Use scheduler.yield() (experimental) or setTimeout(0) to let the browser process pending input between chunks.
3. Web Workers — Offload CPU-intensive work (parsing, sorting, image processing, encryption) to a background thread. Workers run in parallel and don't block the main thread.
4. Defer non-critical work — Use requestIdleCallback for analytics, prefetching, and other non-urgent tasks.
5. Code splitting — Lazy-load JavaScript modules so less code executes on initial page load. Use dynamic import() and React.lazy().
6. Reduce JavaScript execution — Use lighter libraries, remove unused code (tree shaking), and avoid unnecessary re-renders in React (memo, useMemo).
The Scheduler API's yield() method pauses the current task, yields to the main thread for input processing, then resumes where it left off — maintaining task priority:
async function processData() {
for (const item of largeArray) {
doWork(item);
await scheduler.yield(); // Browser handles input, then resumes
}
}This is superior to setTimeout(0) because it doesn't lose task priority and avoids the minimum 4ms delay.
// Monitor long tasks in production
function observeLongTasks() {
if (!('PerformanceObserver' in window)) return;
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Report long tasks to analytics
sendToAnalytics({
type: 'long-task',
duration: Math.round(entry.duration),
startTime: Math.round(entry.startTime),
// Attribution (which script caused it)
attribution: entry.attribution?.[0]?.name || 'unknown',
});
// Log in development
if (process.env.NODE_ENV === 'development') {
console.warn(
`Long task: ${Math.round(entry.duration)}ms`,
entry.attribution
);
}
}
});
observer.observe({ type: 'longtask', buffered: true });
}
observeLongTasks();Product filtering on pages with thousands of items can become a long task. Breaking the filter logic into chunks keeps the UI responsive while results update progressively.
Syntax highlighting and spell-checking in text editors can block the main thread. Using Web Workers for parsing and requestIdleCallback for non-critical analysis keeps typing smooth.
Build a performance monitoring dashboard that uses the Long Tasks API to detect, log, and visualize long tasks in real-time with their attribution and stack traces.