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 propagating through the DOM.
JavaScript provides two methods for this:
-
event.stopPropagation()β Prevents the event from bubbling (or capturing) to ancestor elements, but other event listeners attached to the same element will still execute. -
event.stopImmediatePropagation()β Prevents the event from propagating and stops any remaining event listeners on the current element from executing.
Using stopPropagation()
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.
Using stopImmediatePropagation()
Suppose we have multiple listeners attached to the same element:
child.addEventListener("click", function (event) {
event.stopImmediatePropagation();
console.log("First listener");
});
child.addEventListener("click", function () {
console.log("Second listener");
});
Clicking the child prints:
First listener
The second listener never runs because stopImmediatePropagation() stops both the event's propagation and any remaining listeners on the current element.
| Method | What it does |
|---|---|
stopPropagation() |
Stops the event from reaching ancestor elements, but other listeners on the same element still run. |
stopImmediatePropagation() |
Stops the event from reaching ancestor elements and prevents any remaining listeners on the same element from executing. |
π― 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! π
Top comments (2)
Why do 90% of posts about this subject skip
stopImmediatePropagation?Great catch! I intentionally focused on the fundamentals, but you're absolutely right that
stopImmediatePropagation()deserves a mention too. I've just updated the article with a brief section explaining how it differs fromstopPropagation(). Thanks for the suggestion.