javascript / performance / dom / best-practices

Why Event Delegation Makes JavaScript Faster

Attaching hundreds of event listeners slows the browser down. Event delegation lets you handle all of them with one listener, reducing memory use and simplifying your code.

3 min read
Cover image for Why Event Delegation Makes JavaScript Faster

Why Event Delegation Makes JavaScript Faster

If you've ever attached click handlers to hundreds of DOM elements, you've probably created more work for the browser than necessary.

A better approach is event delegation, a pattern where a single event listener handles events for many child elements.


The problem

Imagine rendering a list with 1,000 buttons.

JavaScript
const buttons = document.querySelectorAll(".delete-btn"); buttons.forEach((button) => { button.addEventListener("click", () => { console.log("Delete item"); }); });

This creates 1,000 separate event listeners.

Modern browsers handle this fine, but unnecessary listeners use more memory and are harder to maintain.


Event delegation

Attach a single listener to a parent element instead.

HTML
<ul id="todo-list"> <li> Buy milk <button class="delete-btn">Delete</button> </li> <li> Learn JavaScript <button class="delete-btn">Delete</button> </li> </ul>
JavaScript
const list = document.getElementById("todo-list"); list.addEventListener("click", (event) => { if (!event.target.matches(".delete-btn")) return; const item = event.target.closest("li"); item.remove(); });

Now you only need one event listener.


Why it works

Events in the browser bubble.

When you click a button, the event travels upward through the DOM tree.

button
   ↑
li
   ↑
ul
   ↑
body
   ↑
document

Your parent element can inspect the original target and decide what to do.


Dynamic content

Newly created elements work without extra listeners.

JavaScript
const li = document.createElement("li"); li.innerHTML = ` New Task <button class="delete-btn">Delete</button> `; document.getElementById("todo-list").appendChild(li);

You don't need extra JavaScript for this.


Handling multiple actions

You can handle several buttons with one listener.

HTML
<button data-action="edit">Edit</button> <button data-action="delete">Delete</button> <button data-action="share">Share</button>
JavaScript
document.body.addEventListener("click", (event) => { const action = event.target.dataset.action; switch (action) { case "edit": console.log("Editing..."); break; case "delete": console.log("Deleting..."); break; case "share": console.log("Sharing..."); break; } });

TypeScript version

TypeScript
const list = document.querySelector<HTMLUListElement>("#todo-list"); list?.addEventListener("click", (event: MouseEvent) => { const target = event.target as HTMLElement; if (!target.matches(".delete-btn")) return; target.closest("li")?.remove(); });

Performance comparison

ApproachEvent ListenersDynamic ElementsMemory Usage
Individual Listeners1000❌ ManualHigher
Event Delegation1✅ AutomaticLower

Best practices

  • Delegate from the closest stable parent.
  • Use event.target.matches() or closest() to identify elements.
  • Avoid delegating everything to document unless necessary.
  • Keep delegated handlers focused on one responsibility.

Common mistakes

Forgetting closest()

JavaScript
// ❌ Might fail if an icon inside the button is clicked if (event.target.matches(".delete-btn")) { }

Instead:

JavaScript
// ✅ Works for nested elements const button = event.target.closest(".delete-btn"); if (button) { console.log("Delete clicked"); }

Delegating too high

JavaScript
document.addEventListener("click", handleEverything);

This works, but you're better off delegating from a closer container so the handler doesn't process clicks it doesn't need to.


Conclusion

Event delegation uses less memory, handles dynamically added elements, and keeps your code easier to maintain. When you're attaching the same event to many similar elements, ask whether a single delegated listener can do the job instead.