JavaScript Event Propagation & Delegation

Master how events travel the DOM and learn the powerful delegation pattern for writing clean, efficient, and dynamic code.

Lesson ProgressStep 1 of 8
Grandparent
Parent
Child
  • Item 1
  • Item 2
  • Item 3
0 EXP

Let's see what happens when you click. You clicked the 'Child' box. Notice how the event 'bubbles' up to its parents?

// Click the 'Child' box to begin...

The Three Phases of Event Propagation

When an event occurs on an element (like a click), it doesn't just happen there. It embarks on a journey through the DOM. This journey has three phases:

  1. Capturing Phase: The event travels *down* from the root (`window`) to the target element's parent.
  2. Target Phase: The event arrives at the target element (the one that was clicked).
  3. Bubbling Phase: The event travels *up* from the target element back to the `window`.

By default, all modern event listeners handle events during the **bubbling phase**. This is the most common and intuitive behavior.

System Check

What is the default phase in which event listeners operate?

Advanced Holo-Simulations

0 EXP

Log in to unlock these advanced training modules and test your skills.


Achievements

🏆
Bubble Master

Correctly identify the order of event bubbling.

🏗️
Delegation Expert

Correctly use event delegation to handle dynamic elements.

✍️
Propagation Stopper

Prove you know how to stop event propagation.

Mission: The Dynamic List

Below is a list. Add **one** event listener to the `ul` (with id `my-list`) that fires an `alert('Clicked!')` **only** when an `li` element is clicked.

A.D.A. Feedback:

> System integrity looks stable. Code seems valid.

Challenge: Order the Event Phases

Drag the event phases into the correct chronological order from first to last.

Target Phase (Element is clicked)
Capturing Phase (Window to Target)
Bubbling Phase (Target to Window)

Challenge: Complete the Syntax

Fill in the missing parts of this event handler to stop the event from bubbling.

parent.addEventListener("", (e) => {if (.matches('.child')) {e.();}});

Consult A.D.A.

Community Holo-Net

Event Delegation: JavaScript's Performance Superpower

In the world of JavaScript, managing events is a core task. You add a listener, a function runs, and magic happens. But as your applications grow, a naive approach can lead to serious performance problems. Adding an event listener to every single button in a list of 1,000 items is not just tedious—it's slow.

This is where **Event Delegation** comes in. It's a simple, elegant pattern that leverages the power of **event bubbling** to write faster, more efficient, and incredibly flexible code.

The Problem: Too Many Listeners

Imagine a to-do list. Each item has a "delete" button. The common-sense approach is to add a listener to each button:

// The "bad" way (for large lists)
const buttons = document.querySelectorAll('.delete-btn');
buttons.forEach(button => {
  button.addEventListener('click', () => {
    // delete item logic...
  });
});

This works, but it has two major flaws:

  • **Performance:** 1,000 list items means 1,000 event listeners stored in memory. This can slow down page load and interaction.
  • **Dynamic Content:** What happens when you add a *new* to-do item? The code above has already run. The new item's delete button **will not** have an event listener and will not work unless you remember to manually add one.

The Solution: Event Delegation

Event delegation flips the problem on its head. Instead of listening on every child, we add **one single event listener** to their common parent (e.g., the <ul> element).

Thanks to event bubbling, any click on a child button will "bubble up" to the parent <ul>. The parent's listener will catch it. Inside that one listener, we simply check **which child** was the original target.

// The "good" way (Event Delegation)
const list = document.querySelector('#todo-list');

list.addEventListener('click', (event) => {
  // Check if the clicked element (event.target)
  // is a delete button.
  if (event.target.matches('.delete-btn')) {
    // Found it! Run the delete logic.
    event.target.parentElement.remove();
  }
});

✔️ Delegation Wins

  • **One listener** for all items.
  • Uses **less memory**.
  • **Automatically works** for new items added later!

❌ Direct Binding Woes

  • **1,000 listeners** for 1,000 items.
  • **High memory** usage.
  • **Fails** for dynamic new items.

A Warning: The Overuse of `stopPropagation()`

When learning about bubbling, it's tempting to use `event.stopPropagation()` to "control" the flow. Be very careful. While it has its uses (e.g., a modal window where clicking inside it shouldn't close it), overusing it can break other parts of your application.

If a parent element *depends* on event delegation (like our list), and a child element calls `stopPropagation()`, the parent listener **will never fire**. It's like cutting the wire. Always try to solve your problem without `stopPropagation()` first.

Key Takeaway: Event delegation is not just a "trick," it's the professional standard for handling events on lists or groups of elements. By listening on a parent and checking `event.target`, you create more performant, resilient, and maintainable JavaScript.

JavaScript Events Glossary

Event
A signal that something has happened in the browser, such as a user click (`click`), a key press (`keydown`), or a page finishing loading (`load`).
Event Listener
A function that "listens" for a specific event to occur on a specific element and then executes. Attached using `addEventListener()`.
Event Propagation
The process of how an event travels through the DOM. It consists of two main phases: the Capturing Phase (down) and the Bubbling Phase (up).
Capturing Phase
The first phase, where the event travels *down* from the `window` (root) to the target element. You can listen in this phase by setting the third argument of `addEventListener()` to `true`.
Bubbling Phase
The third (and most common) phase, where the event travels *up* from the target element back to the `window`. This is the default phase for listeners.
event.target
A property of the event object that refers to the **originating element** that the event was fired on (e.g., the exact <li> that was clicked). It never changes as the event propagates.
event.currentTarget
A property that refers to the element the **event listener is currently attached to**. In event delegation, this is the parent (<ul>), while `event.target` is the child (<li>).
event.stopPropagation()
A method that stops the event from propagating any further in its current phase (e.g., stops bubbling up to parent elements).
event.stopImmediatePropagation()
A more aggressive version of `stopPropagation()`. It not only stops propagation but also prevents any *other* listeners *on the same element* from running.
event.preventDefault()
A method that stops the browser's default behavior for an event. For example, prevents a submit button from submitting a form, or a link from navigating.
Event Delegation
A pattern where a single event listener is added to a parent element to manage events for all of its children. It relies on event bubbling and checking `event.target`.

About the Author

Author's Avatar

TodoTutorial Team

Passionate developers and educators making programming accessible to everyone.

This article was written and reviewed by our team of web development experts, who have years of experience teaching JavaScript and building robust, efficient web applications.

Verification and Updates

Last reviewed: October 2025.

We strive to keep our content accurate and up-to-date. This tutorial is based on the latest DOM event specifications and is periodically reviewed to reflect industry best practices.

External Resources

Found an error or have a suggestion? Contact us!