The Third Argument of addEventListener You've Been Ignoring
The options object on addEventListener has been around since 2016, and I still regularly see codebases that hand-roll everything it does for free.
once: self-removing listeners
button.addEventListener('click', startOnboarding, { once: true });
The listener runs a single time and removes itself. Every "did I already bind this?" boolean flag, every removeEventListener call inside the handler itself - that's what once replaces. It's perfect for one-shot UI like "click anywhere to dismiss", intro animations, or lazily initializing something on first interaction:
// Warm up an audio context on the first user gesture (autoplay policies!)
document.addEventListener('pointerdown', initAudio, { once: true });
passive: the scroll performance one
When you listen for touchstart, touchmove or wheel, the browser has to wait for your handler before it scrolls, because you might call preventDefault(). That wait is measurable jank on mid-range phones.
window.addEventListener('touchmove', trackSwipe, { passive: true });
passive: true is a promise: "I will never call preventDefault() here." The browser stops waiting and scrolling stays smooth regardless of what your handler does. Chrome considered this important enough that document-level touch and wheel listeners are passive by default now - which occasionally surprises people whose preventDefault() silently stopped working. If you genuinely need to block scrolling, set passive: false explicitly and accept the cost.
capture: catching events on the way down
Events travel down the tree to the target (capture phase), then bubble back up. Handlers run in the bubble phase by default. capture: true runs yours on the way down, before the target ever sees it, which is occasionally the only way to intercept events from third-party widgets you don't control - or to log every click on the page even when inner code calls stopPropagation().
document.addEventListener('click', auditLog, { capture: true });
And they combine
el.addEventListener('wheel', onFirstScroll, { once: true, passive: true });
If you're managing lots of listeners, the fourth member of this family is the signal option - I wrote about tearing down whole groups of listeners with AbortController, and together these options cover almost every lifecycle situation without any bookkeeping code.
Full-stack web developer sharing practical tutorials and building tools that ship.
Got something on your mind?
My inbox is open - no forms disappearing into the void here.
- Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
- Hire me for a project Have something custom in mind? Let's talk scope and timelines.
- Product support Bought something here? I'll help you get it running.
I usually reply within 1-2 business days.