Event Bubbling, Capturing, and Event Delegation
Event Delegation is a pattern where a single event listener is attached to a parent element to handle events on its current and future children, leveraging DOM event propagation (bubbling).
Why it matters in interviews
Visual & Interactive Explanation
DOM Event Propagation Flow (Capturing vs Bubbling)
Sequence & Data Exchange Flow
1. Capturing Phase begins down DOM hierarchy
2. Capturing traverses down to Parent container
3. Target Phase: Event fires on clicked Button
4. Bubbling Phase: Event bubbles up to Parent (Delegation catches this!)
5. Bubbling finishes at root Window object
Code Examples & Implementation
// Single event listener on parent container handles 10,000 product cards
const productGrid = document.querySelector('#product-grid') as HTMLElement;
productGrid.addEventListener('click', (event: MouseEvent) => {
const target = event.target as HTMLElement;
// Find closest button with specific data action
const deleteBtn = target.closest('[data-action="delete"]');
const buyBtn = target.closest('[data-action="buy"]');
if (deleteBtn) {
const card = deleteBtn.closest('.product-card');
const productId = card?.getAttribute('data-id');
console.log(`Deleting product ID: ${productId}`);
return;
}
if (buyBtn) {
const card = buyBtn.closest('.product-card');
const productId = card?.getAttribute('data-id');
console.log(`Adding product ID ${productId} to cart`);
}
});Interview-Ready Answers
Events in the DOM travel down from Window to the target (capturing) and bubble back up to Window (bubbling). Event delegation places one listener on a parent and uses event.target to identify which child was clicked.
Official Documentation & Specifications
Follow-up Questions & Deep Dives
🎯 Code Output — What Does This Print?
// HTML: <div id="outer"><div id="inner"><button id="btn">Click</button></div></div>
document.getElementById("outer").addEventListener("click", () => console.log("outer"));
document.getElementById("inner").addEventListener("click", () => console.log("inner"));
document.getElementById("btn").addEventListener("click", () => console.log("btn"));
// User clicks the button. What is the output?// HTML: <div id="parent"><button id="child">Click</button></div>
document.getElementById("parent").addEventListener("click", () => console.log("parent"));
document.getElementById("child").addEventListener("click", (e) => {
console.log("child");
e.stopPropagation();
});
// User clicks the button. What is the output?// HTML: <ul id="list"><li data-id="1"><span>Item 1</span></li></ul>
document.getElementById("list").addEventListener("click", (e) => {
console.log("target:", e.target.tagName);
console.log("currentTarget:", e.currentTarget.tagName);
const li = e.target.closest("li");
console.log("data-id:", li?.dataset.id);
});
// User clicks the <span> inside the <li>Common Mistakes & Anti-Patterns
- Attaching individual click listeners to every list row in a map loop without considering delegation.
- Using event.target instead of event.target.closest('selector'), breaking when users click on nested <span> or <i> icons inside a button.
- Forgetting { passive: true } on scroll and touch listeners, causing scroll lag.
Real-World Architectural Scenario
A table with 10,000 rows causes the browser to freeze for 800ms during initial rendering. Heap analysis shows 30,000 listener closures. How do you resolve this?
Rate Your Readiness
Rate how comfortably you can explain this in an interview to update your global readiness gauge: