The event.stopPropagation() method
event.stopPropagation() stops the event from travelling any further through the DOM tree: the event still fires on the element where it happened, but it never rises to that element's ancestors.
The method only affects the event's route. It does not cancel the browser's default action and it does not block other handlers attached to the same element.
Theory
TL;DR
stopPropagation()cuts off further propagation through the tree: both bubbling upward and any remaining capturing downward.- Handlers attached to the same element still run: the method stops neighbours in the tree, not neighbours on the element.
- It does not cancel the browser's default action (following a link, submitting a form), that is what
preventDefault()is for. stopImmediatePropagation()is stricter: it stops propagation and the remaining handlers on this very element.- Overusing it silently breaks event delegation: the parent handler simply stops receiving clicks.
Quick example
document.querySelector('.child').addEventListener('click', (event) => {
console.log('child');
event.stopPropagation(); // stop bubbling, the parent will see nothing
});Event bubbling by default
Most DOM events bubble: after being handled on the target element they travel up the chain of ancestors.
<div class="parent">
<button class="child">Click</button>
</div>document.querySelector('.parent').addEventListener('click', () => {
console.log('Parent');
});
document.querySelector('.child').addEventListener('click', () => {
console.log('Child');
});Clicking the .child button logs:
Child
ParentThe handler on the button ran first, then the event bubbled up and triggered the parent's handler.
What stopPropagation() actually does
It is enough to call the method inside the child's handler:
document.querySelector('.child').addEventListener('click', (event) => {
console.log('Child');
event.stopPropagation(); // stop the bubbling
});Now a click on the button logs only:
ChildThe event never reaches the parent, it gets stuck on the button. The same holds for deeper nesting:
<div id="outer">
<div id="inner">
<button id="btn">Click me</button>
</div>
</div>outer.addEventListener('click', () => console.log('outer'));
inner.addEventListener('click', () => console.log('inner'));
btn.addEventListener('click', (e) => {
e.stopPropagation();
console.log('button');
});A click on the button prints only button, while inner and outer never receive the event.
Delegation: when bubbling gets in the way
If a single shared handler sits on the parent (classic delegation), a click on a nested button sometimes must not reach it:
<ul id="list">
<li>Item 1 <button>Delete</button></li>
<li>Item 2 <button>Delete</button></li>
</ul>list.addEventListener('click', (e) => {
if (e.target.tagName === 'LI') {
console.log('Item clicked');
}
});
document.querySelectorAll('button').forEach(btn =>
btn.addEventListener('click', (e) => {
e.stopPropagation(); // so the parent does not receive the event
console.log('Deleting the item');
})
);The result:
- a click on
<li>logs "Item clicked"; - a click on the button logs "Deleting the item", and the event does not bubble to the list.
What stopPropagation() does not do
- It does not cancel the browser's default action. The form will still submit, the link will still open. You need
event.preventDefault()for that. - It does not block other handlers on the same element. If a button has three
clicklisteners, all three run, in the order they were added.
stopImmediatePropagation() and the summary table
There is a stricter option:
event.stopImmediatePropagation();It stops everything: propagation through the tree and the remaining handlers on this very element, so further processing is blocked completely.
| Method | What it does | Stops bubbling? | Stops other handlers on the element? |
|---|---|---|---|
event.stopPropagation() | Interrupts propagation further through the tree | Yes | No |
event.stopImmediatePropagation() | Blocks any further processing of the event | Yes | Yes |
event.preventDefault() | Cancels the browser's default action | No | No |
A short mnemonic:
stopPropagation()means "do not go any higher",preventDefault()means "do not do the default thing",stopImmediatePropagation()means "stop everything, even the neighbours".
Common mistakes
- Confusing it with
preventDefault(). The first controls the event's route, the second controls the browser's default action. To both keep a form event from bubbling and keep it from submitting you need both calls. - Adding
stopPropagation()just in case in every handler. That silently breaks event delegation and global listeners ondocument, for example closing a dropdown on an outside click. - Expecting it to disable the other handlers on the same element. It will not, that is what
stopImmediatePropagation()is for. - Forgetting the capture phase. If the listener was added with
{ capture: true }, the call cuts the event off before it even reaches the target element. - Writing
return falseinstead of the call. In a plain DOM handler that does nothing: this behaviour exists only in jQuery, wherereturn falsecalls both methods at once.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.