100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
JavaScript

Event Bubbling and Capturing in JavaScript

Understand the two phases of DOM event propagation — capturing (top-down) and bubbling (bottom-up) — and how to control them.

DOM & EventsIntermediate10 min readJul 8, 2026
Analogies

1. Introduction

When an event occurs on a DOM element that is nested inside other elements, the event does not just fire on that one element — it travels through the DOM tree in a defined sequence called 'event propagation'. This propagation has two phases: capturing (from the root down to the target) and bubbling (from the target back up to the root).

🏏

Cricket analogy: When a boundary is hit at the crease, the news doesn't just register at the batter — it travels outward through non-striker, umpire, and commentary box in sequence, mirroring how a DOM event propagates down (capturing) and back up (bubbling) through ancestor elements.

Understanding propagation is essential for techniques like event delegation, and for correctly stopping unwanted side effects with methods like stopPropagation().

🏏

Cricket analogy: Knowing whether an umpire's signal is meant for the specific fielder or announced to the whole ground is like understanding propagation — essential for 'event delegation' techniques and for stopping an over-eager PA announcement with stopPropagation().

2. Syntax

javascript
// Bubbling phase listener (default: capture = false)
parent.addEventListener('click', (e) => {
  console.log('Parent (bubble phase)');
});

// Capturing phase listener (capture: true)
parent.addEventListener('click', (e) => {
  console.log('Parent (capture phase)');
}, { capture: true });

// Stop the event from propagating further
child.addEventListener('click', (e) => {
  e.stopPropagation();
});

// Prevent the browser's default action for this event
link.addEventListener('click', (e) => {
  e.preventDefault();
});

3. Explanation

Event propagation has three conceptual stages: (1) Capturing phase — the event starts at window/document and travels DOWN through ancestors to the target element; (2) Target phase — the event reaches the actual element that was interacted with; (3) Bubbling phase — the event then travels back UP from the target through its ancestors to document. By default, addEventListener listens during the bubbling phase; passing { capture: true } (or true as the third argument) makes it listen during the capturing phase instead.

🏏

Cricket analogy: A review request starts at the third umpire (capturing down to the field umpire), reaches the actual disputed delivery (target), then the decision travels back up through field umpire to scoreboard (bubbling); listeners default to catching it on the way back up unless { capture: true } is set.

event.stopPropagation() halts further travel of the event through the remaining phase (whether capturing or bubbling), so ancestor (or descendant, if capturing) listeners for that same event will not fire for this particular event instance. This is entirely different from event.preventDefault(), which cancels the browser's default action for the event (like following a link's href or submitting a form) but does NOT stop the event from continuing to propagate to other listeners.

🏏

Cricket analogy: An umpire's stopPropagation() halts a review request from reaching the third umpire, but that's different from overturning the on-field call itself (preventDefault()), which cancels the original decision without stopping the appeal from being logged elsewhere.

Event bubbling underlies 'event delegation': instead of attaching a listener to every child element, you attach a single listener to a common ancestor and use event.target to determine which child was actually interacted with — this is more efficient, especially for dynamically added children.

🏏

Cricket analogy: Instead of hiring a separate scorer for every fielder, one head scorer at the boundary rope watches all events and uses event.target to figure out which fielder actually made the play — this is event delegation, efficient even as fielders rotate.

Gotcha: stopPropagation() and preventDefault() solve different problems and are often confused. stopPropagation() stops the event from reaching other listeners further along the propagation path; preventDefault() stops the browser's built-in default behavior (e.g., link navigation, checkbox toggling, form submission) but the event still bubbles/captures normally unless stopPropagation() is also called.

4. Example

html
<div id="outer">
  Outer
  <div id="inner">
    Inner
    <button id="btn">Click</button>
  </div>
</div>
javascript
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const btn = document.getElementById('btn');

outer.addEventListener('click', () => console.log('outer - capture'), { capture: true });
inner.addEventListener('click', () => console.log('inner - capture'), { capture: true });
btn.addEventListener('click', () => console.log('btn - target'));
inner.addEventListener('click', () => console.log('inner - bubble'));
outer.addEventListener('click', () => console.log('outer - bubble'));

// Simulate clicking the button
btn.click();

5. Output

text
Console output (in this exact order):
outer - capture
inner - capture
btn - target
inner - bubble
outer - bubble

Explanation of order:
1. Capturing phase travels top-down: outer -> inner (before reaching target).
2. Target phase fires the listener(s) directly on btn.
3. Bubbling phase travels bottom-up: inner -> outer.

If inner's bubble-phase listener called event.stopPropagation(), the final
line 'outer - bubble' would NOT print, but the earlier capture-phase lines
would still have printed since propagation had already passed that point.

6. Key Takeaways

  • Event propagation has three stages: capturing (top-down), target, and bubbling (bottom-up).
  • addEventListener listens on the bubbling phase by default; pass { capture: true } for the capturing phase.
  • stopPropagation() halts further travel of the event to other listeners in the remaining phase.
  • preventDefault() cancels the browser's default action but does not stop propagation.
  • Event delegation exploits bubbling to handle events for many/dynamic children with one ancestor listener.

Practice what you learned

Was this page helpful?

Topics covered

#JavaScript#JavaScriptProgrammingStudyNotes#Programming#EventBubblingAndCapturingInJavaScript#Event#Bubbling#Capturing#Syntax#StudyNotes#SkillVeris