All topics
Performanceintermediate

Passive Event Listeners and Scroll Performance

The passive option for addEventListener that tells the browser a handler won't call preventDefault, letting scrolling proceed immediately without waiting.

A passive listener promises not to call preventDefault, which lets the browser scroll without waiting for it.

A passive listener is like telling a security guard in advance 'I'm just here to observe, I promise not to stop anyone' — the guard (browser) can let foot traffic (scrolling) continue flowing immediately without pausing to watch what you do, instead of the default assumption that any observer might suddenly step in front of someone and needs to be watched closely before traffic can proceed.

Key Concepts

1
    el.addEventListener('touchstart', handler, { passive: true });
2
Why the promise matters. A touchstart or wheel handler could cancel the scroll. So without knowing, the browser must run your handler first and only then scroll.
touchstartwheel
3
    non-passive:  event -> run the handler -> then scroll
    passive:      event -> scroll immediately; the handler runs too
4
On a slow handler that is the difference between scrolling that tracks the finger and scrolling that lags.
5
Calling preventDefault in a passive listener does nothing, and logs a warning. That is the contract.
preventDefault
6
Browsers already default to passive for touchstart, touchmove and wheel on window, document and document.body — precisely because so many pages were slow for this reason.
touchstarttouchmovewheelwindowdocument
7
Elsewhere you must say so explicitly.
8
Which is the audit worth running.
    // often unnecessarily blocking
    window.addEventListener('scroll', onScroll);
    el.addEventListener('wheel', onWheel);
9
    // pass the option
    window.addEventListener('scroll', onScroll, { passive: true });
10
Lighthouse flags non-passive listeners, and it is usually a one-word fix.
11
When you cannot be passive. A carousel or a custom gesture that genuinely needs to cancel the default scroll:
12
    el.addEventListener('touchmove', (e) => {
      if (isHorizontalSwipe) e.preventDefault();
    }, { passive: false });
13
Then keep the handler very short, because it is on the critical path for every scroll.
14
Pair it with rAF coalescing.
    let ticking = false;
    window.addEventListener('scroll', () => {
      if (ticking) return;
      ticking = true;
      requestAnimationFrame(() => { update(); ticking = false; });
    }, { passive: true });
rAF
15
Scroll fires far more often than the screen repaints. This does the work once per frame, which is the most it can usefully be done.
16
And the CSS alternative. scroll-behavior, overscroll-behavior and touch-action express common intentions declaratively, with no listener at all — touch-action: pan-y prevents horizontal panning without any JavaScript.
scroll-behavioroverscroll-behaviortouch-actiontouch-action: pan-y
17
What the interviewer is probing.1. "What does passive: true tell the browser?" Probing: the promise. Stalls: "To ignore the event." Moves up: that the handler will not call preventDefault, so scrolling can start immediately rather than waiting for the handler.
18
2. "Why does it matter on touch and wheel events?" Probing: the blocking. Stalls: "It does not." Moves up: without it the browser must run the handler before scrolling to see whether it cancels, which delays the first frame.
19
3. "What happens if a passive handler calls preventDefault?" Probing: the consequence. Stalls: "It works." Moves up: it is ignored with a console warning.
20
4. "What is the default now?" Probing: currency. Stalls: "Not passive." Moves up: touchstart, touchmove and wheel default to passive on document-level listeners in modern browsers.