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 too4
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.