What is event delegation and why should I use it?

HTML, CSS & JS 2 min read
Short answer

Attach a single listener to a container and use event.target.closest() to identify what was clicked. It uses less memory and automatically covers elements added after the listener was attached.

javascript
document.querySelector('.list').addEventListener('click', (event) => {
  const button = event.target.closest('[data-delete]');
  if (!button) return;

  const id = button.dataset.delete;
  removeItem(id);
});

One listener handles every button in the list — including ones added ten minutes from now.

#The alternative does not scale

javascript
document.querySelectorAll('[data-delete]').forEach((button) => {
  button.addEventListener('click', handler);
});

A thousand rows means a thousand listeners. Worse, a row added after this runs has no listener at all, which is the source of "it works until I add a new item".

#Why closest() and not target directly

If the button contains an icon, event.target may be the <svg>, not the <button>. closest() walks up from the target until it finds a match, so it works regardless of what was actually clicked.

The if (!button) return guard is essential — clicks elsewhere in the container will also fire the listener.

#It relies on bubbling

Events travel up the tree from the target. A few do not bubble — focus, blur, and most media events. Use focusin/focusout instead, which do.

You can also capture, which delegates non-bubbling events:

javascript
form.addEventListener('blur', handler, true);   // capture phase

#Keep the handler cheap

It runs on every click inside the container. Do the closest() check and bail out first, before any expensive work.