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.
JavaScriptconst buttons = document.querySelectorAll(".delete-btn"); buttons.forEach((button) => { button.addEventListener("click", () => { console.log("Delete item"); }); });
This creates 1,000 separate event listeners.
Although modern browsers are optimized for this, unnecessary listeners consume additional memory and increase maintenance complexity.
Event Delegation
Instead, attach a single listener to a parent element.
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>
JavaScriptconst 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 only one event listener is required.
Why It Works
Events in the browser bubble.
When a button is clicked, 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
Another advantage is that newly created elements automatically work without adding new listeners.
JavaScriptconst li = document.createElement("li"); li.innerHTML = ` New Task <button class="delete-btn">Delete</button> `; document.getElementById("todo-list").appendChild(li);
No additional JavaScript is needed.
Handling Multiple Actions
You can even support multiple buttons with a single listener.
HTML<button data-action="edit">Edit</button> <button data-action="delete">Delete</button> <button data-action="share">Share</button>
JavaScriptdocument.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
TypeScriptconst 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
| Approach | Event Listeners | Dynamic Elements | Memory Usage |
|---|---|---|---|
| Individual Listeners | 1000 | ❌ Manual | Higher |
| Event Delegation | 1 | ✅ Automatic | Lower |
Best Practices
- Delegate from the closest stable parent.
- Use
event.target.matches()orclosest()to identify elements. - Avoid delegating everything to
documentunless 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
JavaScriptdocument.addEventListener("click", handleEverything);
While this works, it's often better to delegate from a closer container to reduce unnecessary event processing.
Conclusion
Event delegation is one of the simplest performance improvements you can make in JavaScript. It reduces memory usage, supports dynamically added elements, and keeps your codebase easier to maintain.
Whenever you're attaching the same event to many similar elements, consider whether a single delegated listener can do the job instead.


