How an event travels
When you click a button inside a list, the event first travels down from the document to the button (capture phase), then back up from the button through every ancestor (bubble phase). addEventListener listens in the bubble phase unless you pass { capture: true }.
Event delegation
Instead of adding a listener to every item, add one to a common parent and check event.target. It also works for items added later.
<ul id="todo">
<li><button data-id="1">Done</button> Submit lab record</li>
<li><button data-id="2">Done</button> Revise DBMS unit 3</li>
</ul>
<script>
document.getElementById('todo').addEventListener('click', event => {
const button = event.target.closest('button[data-id]');
if (!button) return;
button.closest('li').classList.add('done');
});
</script>closest() climbs from the clicked element to the nearest match, so the code also works when the click lands on an icon inside the button.
The order, shown without a browser
const order = [];
const listeners = { li: () => order.push('li'), ul: () => order.push('ul'), body: () => order.push('body') };
['li', 'ul', 'body'].forEach(el => listeners[el]());
console.log('bubbles up:', order.join(' -> '));bubbles up: li -> ul -> bodypreventDefault and stopPropagation
- event.preventDefault() stops the browser's own action: a link navigating, a form submitting and reloading the page, a checkbox toggling.
- event.stopPropagation() stops the event bubbling to ancestors. Use it rarely; it can break delegated listeners elsewhere on the page.
- For forms, listen to the form's submit event rather than the button's click, so pressing Enter works too.
#javascript#dom#events#event delegation
Sources and further reading
Practise this as an interview question
Written by the UniCircle team, checked by an editor on 22 Oct 2026. It is not official policy of any university. Exam patterns and rules change; confirm dates and rules on the official website.
