Understanding the DOM, Event Propagation, and Event Delegation in JavaScript 🚀

javascript dev.to

If you've interviewed for a Frontend Developer role, chances are you've been asked at least one of these questions:

  • What is the DOM?
  • What is Event Propagation?
  • What's the difference between Event Bubbling and Capturing?
  • What is Event Delegation?
  • What's the difference between event.target and event.currentTarget?

These concepts are closely related, yet they're often explained separately. Once you understand how they connect, JavaScript's event system becomes much easier to reason about.

In this article, we'll walk through these concepts step by step, using practical examples to understand not only what they are, but also why they exist.


🌳 What is the DOM?

Before JavaScript can interact with a webpage, the browser needs a way to represent the HTML document.

That's where the Document Object Model (DOM) comes in.

When the browser loads an HTML page, it parses the HTML and builds an in-memory tree structure called the DOM.

Every HTML element becomes a Node, allowing JavaScript to read, modify, create, or remove elements dynamically.

For example:

<html>
  <body>
    <h1>Hello</h1>
    <p>Welcome!</p>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

becomes something similar to:

Document
│
└── HTML
    ├── HEAD
    └── BODY
        ├── H1
        └── P
Enter fullscreen mode Exit fullscreen mode

This is why methods like:

document.getElementById()
document.querySelector()
document.createElement()
Enter fullscreen mode Exit fullscreen mode

work—they're interacting with the DOM, not the original HTML file.

Because of the DOM, JavaScript can:

  • Change content
  • Update styles
  • Create new elements
  • Remove existing elements
  • Listen for user interactions

Example:

document.querySelector("h1").textContent = "Hello JavaScript!";
Enter fullscreen mode Exit fullscreen mode

Without the DOM, JavaScript wouldn't be able to manipulate the page after it has loaded.


⚡ What is Event Propagation?

One common misconception is that clicking an element only affects that element.

That's not actually how browsers work.

Whenever an event occurs (such as clicking a button), the browser makes that event travel through the DOM tree before JavaScript finishes processing it.

This journey is called Event Propagation.

Every event goes through three phases.


1️⃣ Capturing Phase

The event starts at the top of the DOM hierarchy and travels downward toward the clicked element.

Window
 ↓
Document
 ↓
HTML
 ↓
BODY
 ↓
Grandparent
 ↓
Parent
 ↓
Child
Enter fullscreen mode Exit fullscreen mode

Although developers don't use this phase very often, the browser always performs it.

If needed, we can listen during this phase by passing:

{ capture: true }
Enter fullscreen mode Exit fullscreen mode

to addEventListener().

Example:

parent.addEventListener(
  "click",
  () => {
    console.log("Capturing");
  },
  { capture: true }
);
Enter fullscreen mode Exit fullscreen mode

2️⃣ Target Phase

The event reaches the element that actually triggered it.

For example:

<div id="child">Child</div>
Enter fullscreen mode Exit fullscreen mode

If we click on Child, then Child becomes the event target.


3️⃣ Bubbling Phase

After the target phase finishes, the event starts moving back up through the DOM.

Child
 ↑
Parent
 ↑
Grandparent
 ↑
BODY
 ↑
HTML
 ↑
Document
 ↑
Window
Enter fullscreen mode Exit fullscreen mode

This is known as Event Bubbling, and it's the default behavior in JavaScript.

Most applications rely on bubbling every single day—even if we don't realize it.


Let's See It in Action

HTML:

<div id="grandparent">
  Grandparent
  <div id="parent">
    Parent
    <div id="child">
      Child
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

JavaScript:

document.getElementById("grandparent").addEventListener("click", () => {
  console.log("Grandparent clicked");
});

document.getElementById("parent").addEventListener("click", () => {
  console.log("Parent clicked");
});

document.getElementById("child").addEventListener("click", () => {
  console.log("Child clicked");
});
Enter fullscreen mode Exit fullscreen mode

Clicking the Child prints:

Child clicked
Parent clicked
Grandparent clicked
Enter fullscreen mode Exit fullscreen mode

Why?

Because after the click reaches the target, the event bubbles upward through every parent element.


event.target vs event.currentTarget

This is another interview favorite.

Suppose we attach the listener to the parent.

parent.addEventListener("click", function (event) {
  console.log(event.target);
  console.log(event.currentTarget);
});
Enter fullscreen mode Exit fullscreen mode

If we click on Child:

event.target
Enter fullscreen mode Exit fullscreen mode

returns

<div id="child">
Enter fullscreen mode Exit fullscreen mode

because that's where the event started.

While

event.currentTarget
Enter fullscreen mode Exit fullscreen mode

returns

<div id="parent">
Enter fullscreen mode Exit fullscreen mode

because that's the element whose event listener is currently executing.

event.target event.currentTarget
Element that triggered the event Element that owns the current listener

Understanding this difference makes debugging event-related issues much easier.


Stopping Event Propagation

Sometimes we don't want an event to continue bubbling.

JavaScript provides:

event.stopPropagation();
Enter fullscreen mode Exit fullscreen mode

Example:

child.addEventListener("click", function (event) {
  event.stopPropagation();

  console.log("Child clicked");
});
Enter fullscreen mode Exit fullscreen mode

Now clicking the child won't trigger the parent's or grandparent's click handlers.


🎯 What is Event Delegation?

Imagine building a Todo application.

Every time the user adds a task, a new <li> is created.

A common approach is:

items.forEach(item => {
    item.addEventListener("click", ...);
});
Enter fullscreen mode Exit fullscreen mode

This means creating one event listener for every item.

That works...

Until your application has hundreds—or even thousands—of elements.

A much better solution is to attach one listener to the parent.

const parent = document.querySelector("ul");

parent.addEventListener("click", function (event) {
    const item = event.target.closest("li");

    if (!item) return;

    console.log("Clicked:", item.textContent);
});
Enter fullscreen mode Exit fullscreen mode

Now we only have one event listener, regardless of how many list items exist.


Why does this work?

Because of Event Bubbling.

The click starts on the <li>.

Then it bubbles to the <ul>.

The parent receives the event and checks:

event.target
Enter fullscreen mode Exit fullscreen mode

to determine which child triggered it.

This technique is called Event Delegation.


Why use closest() instead of tagName?

Consider this HTML:

<li>
    <span>🗑 Delete</span>
</li>
Enter fullscreen mode Exit fullscreen mode

If the user clicks on the <span>:

event.target.tagName
Enter fullscreen mode Exit fullscreen mode

returns:

SPAN
Enter fullscreen mode Exit fullscreen mode

not

LI
Enter fullscreen mode Exit fullscreen mode

So checking:

event.target.tagName === "LI"
Enter fullscreen mode Exit fullscreen mode

would fail.

Using:

event.target.closest("li")
Enter fullscreen mode Exit fullscreen mode

searches upward until it finds the nearest <li>, making the solution much more reliable.


Why is Event Delegation useful?

Instead of writing:

li.addEventListener(...)
li.addEventListener(...)
li.addEventListener(...)
Enter fullscreen mode Exit fullscreen mode

we simply write:

ul.addEventListener(...)
Enter fullscreen mode Exit fullscreen mode

Benefits include:

  • Better performance
  • Lower memory usage
  • Cleaner code
  • Easier maintenance
  • Works automatically for dynamically added elements

For example:

const li = document.createElement("li");

li.textContent = "Item 4";

parent.appendChild(li);
Enter fullscreen mode Exit fullscreen mode

Even though Item 4 didn't exist when the event listener was registered, clicking it still works because the parent is listening for all click events.


Quick Summary

Concept Description
DOM A tree representation of the HTML document that JavaScript can manipulate
Event Propagation The journey an event takes through the DOM
Capturing Event travels from the Window down to the target
Target The element that originally triggered the event
Bubbling Event travels back up through the DOM
Event Delegation Attaching a single event listener to a parent instead of multiple child elements
event.target The element that triggered the event
event.currentTarget The element whose listener is currently executing

Final Thoughts

The biggest realization for me was that these aren't separate JavaScript topics—they're all different pieces of the same mechanism.

The browser builds the DOM, events travel through that DOM using Event Propagation, and Event Delegation takes advantage of Event Bubbling to reduce the number of event listeners we need.

Once you connect these ideas together, JavaScript's event system becomes much easier to understand, and many frontend interview questions suddenly have straightforward answers.

If you're preparing for frontend interviews, mastering these concepts will give you a much deeper understanding of how browsers handle user interactions—not just how to write event handlers.

Happy coding! 🚀

Source: dev.to

arrow_back Back to Tutorials