In short
A list with 1,000 items doesn't need 1,000 click listeners. Put one listener on the parent and let the clicks bubble up to it. Less memory, and new items just work.
Attaching one listener to a parent element instead of many to its children, using event bubbling and `event.target` to figure out what was actually clicked. Delegation relies on event bubbling and can reduce listener overhead while automatically supporting dynamically inserted child elements.

Think of it like this
The class monitor
The teacher doesn't listen to all 60 students separately. The class monitor collects every complaint and tells the teacher who it came from. The parent element is the monitor, and event.target tells you which student raised their hand.
Watch it happen
One listener on the list
1/5You click the delete button inside "Pay fees". That's event.target.
The big ideas
What you need to know
It rides on bubbling
A click on a child also reaches the parent. So one listener on the parent hears clicks from every child.
Find the real target
event.target is what was actually clicked. event.target.closest(".btn") finds the nearest matching ancestor.
Works for future items
Items added later need no new listener. The parent is already listening for them.
Not every event bubbles
focus and blur don't bubble. Use focusin and focusout when you need delegation for them.
See it in code
Walk through the code
1// <ul id="todos"><li data-id="1">Buy milk <button class="del">x</button></li></ul>2const list = document.querySelector("#todos");3 4list.addEventListener("click", (event) => {5 const delBtn = event.target.closest(".del");6 if (!delBtn) return; // click wasn't on a delete button7 8 const item = delBtn.closest("li");9 console.log("Deleting todo", item.dataset.id);10 item.remove();11});12 13// Items added later work too, no extra listener needed14list.insertAdjacentHTML("beforeend",15 '<li data-id="2">Pay fees <button class="del">x</button></li>');1/4One click listener on the whole list, not one per item.
closest walks up from the clicked element to find a match. data-id lets each item carry its own id, read back with dataset.id.
Save yourself the pain
Common mistakes
Oops
Checking event.target.classList.contains("del") directly.
Do this instead
If the button has an icon inside, target is the icon. Use event.target.closest(".del") instead.
Oops
Adding a listener to each item inside a loop, then again every time you re-render.
Do this instead
You pile up duplicate listeners. Add one listener on the parent, once.
Your mission
One listener, many buttons
Quick check
You add one click listener to a <ul>. Later, your code adds a new <li>. What happens when you click the new <li>?
Go deeper
- 1Convert a list with per-item click listeners into a single delegated listener. Validate your understanding by applying it in a small practical example.
- 2Learn how `event.target` differs from `event.currentTarget`. Focus on understanding the underlying reason it works, not only memorizing the definition.
- 3Understand why delegation is essential for dynamically added elements.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.