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.targetandevent.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>
becomes something similar to:
Document
│
└── HTML
├── HEAD
└── BODY
├── H1
└── P
This is why methods like:
document.getElementById()
document.querySelector()
document.createElement()
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!";
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
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 }
to addEventListener().
Example:
parent.addEventListener(
"click",
() => {
console.log("Capturing");
},
{ capture: true }
);
2️⃣ Target Phase
The event reaches the element that actually triggered it.
For example:
<div id="child">Child</div>
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
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>
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");
});
Clicking the Child prints:
Child clicked
Parent clicked
Grandparent clicked
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);
});
If we click on Child:
event.target
returns
<div id="child">
because that's where the event started.
While
event.currentTarget
returns
<div id="parent">
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();
Example:
child.addEventListener("click", function (event) {
event.stopPropagation();
console.log("Child clicked");
});
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", ...);
});
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);
});
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
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>
If the user clicks on the <span>:
event.target.tagName
returns:
SPAN
not
LI
So checking:
event.target.tagName === "LI"
would fail.
Using:
event.target.closest("li")
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(...)
we simply write:
ul.addEventListener(...)
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);
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! 🚀