Stop 04 · JavaScriptTopics · 25/36

Event Delegation

Part of the Frontend Developer Roadmap

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.

The jaadu mascot as class monitor collects notes from students and passes them to the teacher.

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

ul#todos (listener)
li Buy milk
li Pay fees
button.del
button.del

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

js
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

~20 min

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

  1. 1Convert a list with per-item click listeners into a single delegated listener. Validate your understanding by applying it in a small practical example.
  2. 2Learn how `event.target` differs from `event.currentTarget`. Focus on understanding the underlying reason it works, not only memorizing the definition.
  3. 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.

Download
InsideEdge