Event delegation uses one ancestor listener instead of many child listeners. The production value is handling dynamic DOM, nested targets, stopPropagation surprises, and list performance without attaching handlers everywhere.
Explain Event Delegation in JavaScript
The Core Idea
Event delegation lets you attach one event listener to a common ancestor instead of wiring every child separately. The production/debug value is not just fewer listeners. It is surviving dynamic DOM updates, nested click targets, and propagation edge cases without silently handling the wrong element.
// Example: Without delegation
const items = document.querySelectorAll('li');
items.forEach(item => item.addEventListener('click', () => {
console.log('Clicked:', item.textContent);
}));
// Example: With event delegation
const list = document.querySelector('ul');
list.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
console.log('Clicked:', event.target.textContent);
}
});
Concept | Explanation | Example / Note |
|---|---|---|
Event Bubbling | Events start from the target element and bubble up to its ancestors. |
|
event.target | The original element that triggered the event. | Used to identify which child was clicked |
event.currentTarget | The element that the event listener is attached to. | Usually the parent in delegation |
Why Use Event Delegation?
- Better performance — fewer event listeners in the DOM.
- Handles dynamic elements added later (since the listener is on a stable ancestor).
- Cleaner, more maintainable code for lists, tables, or repeated components.
// Example: Dynamic content
const container = document.querySelector('#buttons');
container.addEventListener('click', (e) => {
if (e.target.matches('button.delete')) {
e.target.remove();
}
});
// Works even if buttons are added later!
Common Pitfalls
- Forgetting to check
event.target→ all clicks bubble and trigger the handler.
- Relying on
event.targetwhen nested elements are inside clickable items (use.closest()to handle this safely).
// Example: Using closest() for nested targets
list.addEventListener('click', (event) => {
const li = event.target.closest('li');
if (!li) return; // click outside any li
console.log('Clicked item:', li.textContent);
});
Practical scenario
A long, dynamic list of comments can be handled with a single click listener on the list container.
Common pitfalls
- Using
event.targetdirectly instead ofclosest()to find the intended item. - Forgetting that
stopPropagationcan block delegation. - Not handling keyboard events, so accessibility suffers.
Delegation saves memory but adds selector logic. Test by adding/removing items dynamically and ensuring handlers still work.
Imagine a restaurant. Instead of each waiter (child) taking orders, there’s one manager (parent) who hears all requests and decides who called — that’s event delegation!
- Attach listener on parent, not each child.
- Use event bubbling +
event.target(orclosest()) to detect the real source.
- Improves performance and works for dynamically added elements.
Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.