Skip to main content

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 menu
  • stopPropagation() 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)
  • stopPropagation alone will not prevent a form from submitting. You need preventDefault for that.
  • Decision rule: unwanted browser behavior → preventDefault; unwanted handler chain → stopPropagation

Quick example

javascript
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 via fetch instead of reloading the page → preventDefault
  • A button inside a card where the card also has a click handler → stopPropagation on the button
  • Modal with a backdrop close: clicking inside should not close it → stopPropagation on the modal content
  • Custom dropdown over a link → both, preventDefault first

Comparison table

AspectpreventDefault()stopPropagation()
TargetsBrowser default actionEvent travel through DOM
DOM traversalUnaffected, parents still fireBlocked, parents skip
Flag setevent.defaultPrevented = trueevent.cancelBubble = true
Typical use<a> without navigation, form without reloadModal backdrop, nested card handlers
Effect on the otherNoneNone

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

javascript
// 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

javascript
elem.addEventListener('click', fn1); elem.addEventListener('click', fn2); // fn2 still runs even if fn1 calls stopPropagation

stopPropagation only stops the event from reaching other elements. For same-element handlers, use stopImmediatePropagation().

Mistake 3: modal without stopPropagation on the content

javascript
// 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() in onClick for dropdowns and nested components; Vue has @click.stop and @click.prevent as shorthands
  • Form handlers in SPAs: e.preventDefault() before calling fetch
  • jQuery: .preventDefault() and .stopPropagation() as separate wrapper calls
  • Shadow DOM: stopPropagation does 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

html
<a href="https://example.com" id="link">Go to Example</a>
javascript
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

jsx
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 fires

This 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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?