All topics
DOMintermediate

Event Delegation Pattern

Attaching a single event listener to a common ancestor instead of many listeners on individual children, relying on event bubbling.

Event delegation is a practical pattern built directly on event bubbling: instead of attaching a separate event listener to every individual child element (especially ones that might not exist yet, or that get added/removed dynamically), you attach one listener to a shared, stable ancestor, and inspect event.target inside that single handler to figure out which specific descendant actually triggered it. This is one of the most practically useful patterns in everyday frontend work, and it's a natural interview follow-up once bubbling is understood.

Event delegation is like a single receptionist at a building's front desk handling every visitor's request instead of stationing a separate employee at every single office door — when someone shows up, the receptionist just checks which office they're actually asking about (event.target.closest) rather than needing dedicated staff hired and fired every time an office opens or closes.

Key Concepts

1
The core mechanic: because a click on any descendant bubbles all the way up through every ancestor by default, a listener on a parent <ul> will still fire for a click on any current or future <li> inside it, without needing to re-attach a new listener every time an item is added or removed. Inside the handler, event.target refers to the actual element that was clicked (the deepest element in the tree at the click point), which might be a <span> inside the <li> rather than the <li> itself — so delegated handlers typically use event.target.closest(selector) to reliably find the relevant ancestor element matching a particular pattern, regardless of exactly which nested element was clicked.
<ul><li>event.target<span>event.target.closest(selector)
2
The performance and maintenance benefits are significant for large or dynamic lists: attaching and removing hundreds of individual listeners as list items are added and removed is both slower and more error-prone (a classic source of memory leaks if listeners aren't properly cleaned up) than maintaining a single, permanent listener on the container that automatically covers every item, present or future, without any extra wiring.
3
The tradeoff is that not every event bubbles (some, historically, like focus/blur in their original non-bubbling form, needed workarounds, though modern focusin/focusout do bubble), and delegation adds a small amount of per-event logic overhead (checking event.target against a selector) compared to a listener that's already scoped to the exact element, though this cost is negligible in virtually all real-world cases.
focusblurfocusinfocusoutevent.target