Event Capturing
Introduction
Event capturing is the phase before the target handles the event. The event travels from the top of the tree down toward the target, giving ancestors an early chance to observe or intercept it.
In modern JavaScript, you opt into this phase with addEventListener(type, handler, { capture: true }) or the legacy third argument true.
Why This Matters
Capturing is less common than bubbling, so it is a strong interview differentiator. It matters for global shortcuts, analytics, overlays, and framework internals that need to observe events before local components stop propagation.
Theory
The three phases
The browser dispatch algorithm has three conceptual phases:
- Capturing phase: ancestors from the root down to the target's parent.
- Target phase: listeners on the target itself.
- Bubbling phase: ancestors from the target's parent back upward.
A listener registered with capture: true runs during capture. A listener registered without it runs during target or bubble depending on where it is placed.
The old useCapture flag
The third parameter to addEventListener used to be a boolean: true for capture and false for bubble. Modern code prefers an options object because { capture: true, once: true } is self-documenting and extensible.
Why use capture?
Use capture when an ancestor must run before children: outside-click boundaries, global keyboard policies, analytics that should run before bubbling is stopped, or a modal layer that needs first chance at pointer events.
Caveats
Capturing can surprise teammates because most UI code assumes bubbling. Use it intentionally, name handlers clearly, and avoid changing behavior globally unless the feature truly needs precedence.
Visual Diagrams
root capture listener | v panel capture listener | v button target listeners | ^ panel bubble listener | ^ root bubble listener
Capture runs top-down before target; bubbling runs bottom-up after target.
addEventListener('click', handler, true)
means capture
addEventListener('click', handler, { capture: true })
means capture and can add once, passive, or signal laterThe options object is clearer in production code than a mysterious boolean.
Code Examples
Run an ancestor listener during capture
The page-level capture listener runs before the button's regular click listener.
Capture with lifecycle cleanup
Use an AbortController when capture listeners belong to a component lifecycle.
Playground
Press Run to execute the code and see output here.
Output Prediction
Predict the output #1
1const path = ['root', 'panel', 'button'];2 3for (let i = 0; i < path.length - 1; i++) {4 console.log('capture ' + path[i]);5}6 7console.log('target ' + path[path.length - 1]);8 9for (let i = path.length - 2; i >= 0; i--) {10 console.log('bubble ' + path[i]);11}Coding Exercises
Compute the full propagation order
MediumImplement getPropagationOrder(path) where path is ordered from root to target. Return labels for capture, target, and bubble phases.
Interview Questions
1What is event capturing and how do you register a capture listener?
Capturing is the top-down propagation phase before the event reaches its target. Register a listener with addEventListener('click', handler, { capture: true }) or the older third argument true.
Follow-ups
- When would capture be useful?
- How is it different from bubbling?
2What is the order of event phases?
The order is capturing, target, then bubbling. Capture listeners on ancestors run from outermost to innermost, target listeners run on the target, and bubble listeners on ancestors run from innermost to outermost.
Quiz
1. How do you opt into the capture phase with modern `addEventListener` syntax?
2. Which phase runs first for an event dispatched to a nested target?
Summary
- Capturing is the top-down phase before the event reaches the target.
- Use `{ capture: true }` rather than an unexplained boolean when possible.
- The full order is capture → target → bubble.
- Capture is powerful but should be used intentionally because most UI code expects bubbling.
Cheat Sheet
Order: capture → target → bubble.
Register: addEventListener(type, handler, { capture: true }).
Legacy: third argument true means capture.
Use cases: global shortcuts, outside-click boundaries, analytics before child stops propagation.
Caution: surprising if overused; document why a capture listener exists.