Event Delegation
Event delegation attaches one listener at a container and uses event bubbling plus target inspection to handle events for many descendants, cutting listener overhead and surviving dynamic children.
Definition
Event delegation registers a single listener on an ancestor element and dispatches based on event.target and closest-matching selectors when events bubble up. It replaces per-child listeners, keeps memory and registration cost constant, and automatically covers children added after setup.
Context and scope
Used for long lists, tables, chat logs, and any container whose children are dynamic. Also the pattern behind the capture-phase variant for focus and other non-bubbling events.
Common misconceptions
- Delegation is only for performance — it is also an architecture: one handler, selector-based dispatch, dynamic children covered
- All events can be delegated — focus and blur do not bubble and need the capture phase or focusin and focusout
- Delegation finds the target — event.target is the deepest node hit, which may be an inner span, so closest is mandatory
Anti-patterns
- Attaching hundreds of identical listeners to list rows instead of one on the list
- Matching on event.target directly and missing clicks on nested child elements inside rows
- Delegating through containers that call stopPropagation in library code and silently swallowing events
Related concepts
Related examples
Authoritative source
https://developer.mozilla.org/en-US/docs/Web/API/Event_Target
What it is
Bubbling carries an event from the deepest target up through ancestors. One listener at the top can therefore serve an entire subtree: inspect the propagation path, find the first element matching a selector, and dispatch. Registration count stops scaling with item count.
How it works
A handler checks event.target.closest(selector) and acts on the match, optionally using event.currentTarget for the container. Capture-phase listeners catch non-bubbling events like focus by listening before descendants. stopPropagation anywhere in the path breaks delegation, which is the debugging signature.
When to use it
Dynamic lists, tables with action buttons, menus, and document-level keyboard handling. Inside shadow DOM, composed events retarget at boundaries, so delegation crossing a shadow boundary needs composedPath inspection.
Pitfalls
Heavy selector logic per event can cost more than direct listeners in hot paths such as mousemove. this and currentTarget differ from older jQuery-era habits. Passive semantics still apply per registration: delegated scroll observers should be passive too.