Lesson 14 of 30
A single script that runs past 50ms without yielding blocks everything else the main thread could be doing — including responding to the click that's waiting behind it.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
By the standard definition, what makes a piece of JavaScript execution a "long task"?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A long task has a precise, standardized definition: any single piece of JavaScript that occupies the main thread for more than 50ms without yielding back to the browser. While a long task is running, the browser cannot do anything else on that thread — it can't handle a pending click, it can't paint a frame, it can't respond to a keypress. Everything queues up behind it until it finishes.
This is the direct mechanism behind the Input Delay sub-part of INP: if a user clicks while a long task is mid-execution, the browser has already received that click, but can't start processing it until the current task yields the thread. The click sits waiting the whole time — Input Delay is, quite literally, "how long the click had to wait for the main thread to become free."
The fix isn't always "make the work faster" — often the work is unavoidable, and the real fix is making sure it never occupies the thread for more than 50ms at a stretch. There are three genuinely different tools for that, and picking the right one matters:
// Blocking: one long task, however long processAll(items) actually takes
function processAll(items) {
for (const item of items) heavyCompute(item);
}
// Yielding: chunks the work, giving the browser a chance to handle input between chunks
async function processAllYielding(items) {
for (let i = 0; i < items.length; i++) {
heavyCompute(items[i]);
if (i % 50 === 0) {
await new Promise((resolve) => setTimeout(resolve, 0)); // yield to the event loop
// or, where supported: await scheduler.yield();
}
}
}
setTimeout(fn, 0) doesn't run instantly — but it does hand control back to the browser first, which is exactly the point: any pending input or paint gets a chance to happen before the next chunk starts. The newer scheduler.yield() API does the same thing more precisely, and navigator.scheduling.isInputPending() lets a long-running task check whether it should yield right now because input is already waiting, instead of yielding on a fixed schedule regardless of need.
If the work is genuinely independent of the DOM — computing on a dataset, parsing something large, running a pure algorithm — moving it to a Web Worker removes it from the main thread's task queue entirely instead of just chunking it there (see the Frontend Practices Web Workers lesson for the mechanics of postMessage and structured cloning; this isn't a Worker API re-explainer).
And for work that isn't urgent at all, requestIdleCallback defers it until the browser genuinely has spare time — after layout, paint, and any pending user-visible work are already handled — rather than competing with them.
heavyCompute calls; it changes whether input can be handled during that work instead of only after all of it finishes.document/window at all — moving DOM-dependent work there isn't an option; yielding or reducing the work are the only choices left.isInputPending() lets a task yield only when it actually needs to, instead of yielding on a fixed interval that might be wastefully frequent (interrupting genuinely uninterrupted work for no reason) or too infrequent (still blocking input for stretches)."A button click feels laggy under load — how do you find out why, and fix it?" is a standard senior-frontend question, and identifying a long task as the specific cause of Input Delay — then reaching for yielding, a worker, or requestIdleCallback based on what the work actually needs — is a stronger answer than a vague "optimize the JavaScript."