Difference between event.preventdefault() and event.stoppropagation()
preventDefault vs stopPropagation - one cancels the browser's built-in reaction to an event, the other stops the event from reaching parent elements in the DOM. They are independent, and calling one has zero effect on the other.
Theory
TL;DR
preventDefault()blocks what the browser does after the event: navigate to a URL, submit a form, show a context menustopPropagation()blocks where the event goes next: up through parent and ancestor elements- Analogy:
preventDefault= ignoring a doorbell (the ring happened, but no one opens the door);stopPropagation= soundproofing the walls (neighbors never hear it) stopPropagationalone will not prevent a form from submitting. You needpreventDefaultfor that.- Decision rule: unwanted browser behavior →
preventDefault; unwanted handler chain →stopPropagation
Quick example
document.getElementById('parent').addEventListener('click', () => {
console.log('Parent fired');
});
document.getElementById('child').addEventListener('click', (e) => {
e.stopPropagation(); // Parent handler won't run
console.log('Child fired');
});
// With stopPropagation: "Child fired" only
// Without it: "Child fired", then "Parent fired"The parent listener stays silent because stopPropagation cuts off the bubble. Calling preventDefault here would do nothing since a plain <button> inside a <div> has no built-in browser action.
Key difference
preventDefault() tells the browser to skip its built-in response: don't follow the href, don't reload the page on form submit. The event still travels the DOM normally after this call. stopPropagation() cuts off the event's journey so parent handlers never fire, but the browser default still runs unless you also called preventDefault(). Two separate flags on the same event object.
When to use
<a>tag with custom routing instead of navigating →preventDefault<form>sending data viafetchinstead of reloading the page →preventDefault- A button inside a card where the card also has a click handler →
stopPropagationon the button - Modal with a backdrop close: clicking inside should not close it →
stopPropagationon the modal content - Custom dropdown over a link → both,
preventDefaultfirst
Comparison table
| Aspect | preventDefault() | stopPropagation() |
|---|---|---|
| Targets | Browser default action | Event travel through DOM |
| DOM traversal | Unaffected, parents still fire | Blocked, parents skip |
| Flag set | event.defaultPrevented = true | event.cancelBubble = true |
| Typical use | <a> without navigation, form without reload | Modal backdrop, nested card handlers |
| Effect on the other | None | None |
How the browser handles this
When an event fires, it travels in three phases: capture (root down to target), target, then bubble (target back up to root). preventDefault sets a flag the browser reads after the full dispatch cycle, right before executing its built-in action. stopPropagation exits the dispatch loop early so remaining bubble-phase listeners never run. One thing worth knowing: if a parent listener registers with { capture: true }, it fires before any child handler runs. Calling stopPropagation in the child does not undo what already fired during capture.
Common mistakes
Mistake 1: using stopPropagation to block a form submit
// Wrong: form still submits
submitButton.addEventListener('click', (e) => {
e.stopPropagation();
});
// Correct
submitButton.addEventListener('click', (e) => {
e.preventDefault();
});stopPropagation controls event flow, not browser behavior.
Mistake 2: expecting it to block other handlers on the same element
elem.addEventListener('click', fn1);
elem.addEventListener('click', fn2); // fn2 still runs even if fn1 calls stopPropagationstopPropagation only stops the event from reaching other elements. For same-element handlers, use stopImmediatePropagation().
Mistake 3: modal without stopPropagation on the content
// Bug: any click inside the modal bubbles up to overlay and fires closeModal
overlay.addEventListener('click', closeModal);
// Fix
modalContent.addEventListener('click', (e) => e.stopPropagation());Real-world usage
- React:
e.stopPropagation()inonClickfor dropdowns and nested components; Vue has@click.stopand@click.preventas shorthands - Form handlers in SPAs:
e.preventDefault()before callingfetch - jQuery:
.preventDefault()and.stopPropagation()as separate wrapper calls - Shadow DOM:
stopPropagationdoes not cross shadow boundaries by default
Follow-up questions
Q: What is the difference between stopPropagation and stopImmediatePropagation?
A: stopPropagation prevents the event from reaching parent elements, but other handlers on the same element in the same phase still run. stopImmediatePropagation blocks both: no more handlers on the current element and no parent handlers either.
Q: Does preventDefault work the same in the capture phase?
A: Yes. preventDefault is phase-independent. The browser checks the flag after the full dispatch cycle, not after each phase. You can call it at any point during the event's journey.
Q: Can you call preventDefault on a custom event?
A: Yes, if the event was created with { cancelable: true }. Without that flag, preventDefault has no effect and event.defaultPrevented stays false.
Q: In React, does e.stopPropagation() stop native DOM propagation?
A: No. React uses synthetic events with its own delegation system. e.stopPropagation() stops propagation inside React's tree only. To block native DOM propagation, use e.nativeEvent.stopImmediatePropagation().
Q: In a shadow DOM, does stopPropagation cross the shadow boundary?
A: No. Events are retargeted at shadow boundaries and stay confined to their tree. To pierce the boundary intentionally, dispatch a CustomEvent with { composed: true }.
Examples
Preventing link navigation
<a href="https://example.com" id="link">Go to Example</a>document.getElementById('link').addEventListener('click', (e) => {
e.preventDefault();
console.log('Link clicked, navigation blocked');
// Page does not change, custom logic runs instead
});The event fired and bubbled normally through the DOM. Only the browser's navigation response was cancelled. Parent elements with click handlers would still receive this event.
React modal with stopPropagation
function Modal({ onClose }) {
return (
<div className="overlay" onClick={onClose}>
<div className="content" onClick={(e) => e.stopPropagation()}>
<p>Click here - modal stays open</p>
<button onClick={onClose}>Close</button>
</div>
</div>
);
}
// Click on .content: stopPropagation prevents bubble to .overlay, onClose does not fire
// Click on .overlay directly: onClose firesThis is one of the most common patterns for modal components, and forgetting stopPropagation on the content div is a bug I have debugged more than once in production. The click travels up to the overlay and closes the modal before the user even realizes what happened.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.