skip to content

In DOM event handling, what does event.preventDefault() do that event.stopPropagation() does not, and what does event.defaultPrevented tell a listener that runs later in the path?

level: middleimportance: must knowfreq 68%

answer

  1. two independent axes
  2. the browser's own reaction vs the trip
  3. one of them is silently ignored sometimes
  4. a flag other listeners can read
  5. returning false buys you nothing here

basics

~20 s

preventDefault() cancels the browser's built-in reaction, such as following a link or submitting a form, while the event keeps travelling. stopPropagation() stops the event reaching other nodes but leaves the default action intact. defaultPrevented reports whether cancellation already happened.

solid answer

~40 s

They act on two independent axes. `preventDefault()` cancels the *browser's* default action — navigating an `<a href>`, submitting a `<form>`, checking a checkbox, showing the context menu — and does nothing to propagation, so every remaining listener still runs. `stopPropagation()` halts the *dispatch*, so no further node in the path is visited, but the default action still happens because cancelling it was never asked for. Two more details matter. `preventDefault()` only has an effect when `event.cancelable` is `true`; on a non-cancelable event the call is silently ignored. And once cancelled, `event.defaultPrevented` reads `true` for the rest of the dispatch, which lets a later handler — typically a delegated one on `document` — see that something nearer the target already claimed the interaction and stand down.

code

javascript · 14 lines
javascript
const form = document.createElement('form');
document.body.append(form);

form.addEventListener('submit', (e) => {
  e.preventDefault();
  console.log('cancelable:', e.cancelable, 'defaultPrevented:', e.defaultPrevented);
});

document.addEventListener('submit', (e) => {
  // still runs: preventDefault does not stop propagation
  console.log('document sees it, already cancelled:', e.defaultPrevented);
});

form.requestSubmit();

go deeper

for a junior

Recall the one-liner: preventDefault stops what the browser would do, such as following a link; stopPropagation stops the event reaching other elements. Know that return false does not work with addEventListener.

for a middle

Explain that the two are independent, name the cancelable precondition, and describe defaultPrevented as a flag shared by every listener in the path.

for a senior

Show judgment about blast radius: reach for defaultPrevented over stopPropagation so ancestor concerns keep working, and know that passive listeners cannot cancel at all.

for a principal

Own it as a contract between teams: define whether components in your system claim interactions by cancelling or by stopping propagation, and document it, because the choice determines whether page-level features stay composable.

## Two axes, not two flavours of the same thing A dispatched event has two things attached to it that a handler might want to suppress: 1. **The default action** — what the *browser* does on its own once handlers have had their say: follow a link, submit a form, toggle a checkbox, scroll on space, open a context menu, drop a dragged file. 2. **The propagation** — the remaining walk over the path, and therefore the other listeners waiting on it. `preventDefault()` touches only the first. `stopPropagation()` touches only the second. Neither implies the other, and the frequent claim that `preventDefault()` "stops the event" is simply wrong. ```javascript link.addEventListener('click', (e) => { e.preventDefault(); // page does not navigate // the click still reaches the parent, document, and window }); ``` ## Cancelable is a precondition Events carry a read-only `cancelable` flag. `preventDefault()` on an event where it is `false` does nothing at all — no error, no effect. Many events that look interruptible are not: `scroll` is not cancelable, and neither are most events you dispatch yourself unless you construct them with `{ cancelable: true }`. Before concluding that a cancellation "did not work", read `event.cancelable`. There is one more way `preventDefault()` becomes a no-op: a listener registered as passive is contractually forbidden from cancelling, and browsers ignore the call (with a console warning). Browsers make `touchstart`, `touchmove` and `wheel` listeners on the document-level targets passive by default for scroll performance. ## defaultPrevented as a coordination channel `event.defaultPrevented` is a boolean that becomes `true` the moment any listener successfully cancels, and stays `true` for the rest of the dispatch. Because the event object is shared by every listener in the path, this is a legitimate, framework-free way for nested handlers to coordinate: ```javascript // A component nearest the click claims the interaction: menuItem.addEventListener('keydown', (e) => { if (e.key === 'Escape') e.preventDefault(); }); // A global handler defers to whoever already handled it: document.addEventListener('keydown', (e) => { if (e.defaultPrevented) return; if (e.key === 'Escape') closeTopmostOverlay(); }); ``` This pattern is worth contrasting with the alternative — calling `stopPropagation()` in the inner handler. Both stop the outer handler from acting, but `stopPropagation()` does it by making the outer handler *never run*, which also silences unrelated listeners on the way (analytics, focus tracking, outside-click dismissal). `defaultPrevented` leaves everyone running and merely publishes a fact they can choose to respect. In shared codebases the second is the more cooperative default. ## Return values that tell you the same thing `EventTarget.dispatchEvent()` returns `false` when a listener cancelled a cancelable event, and `true` otherwise. That is how the code that dispatched a custom event learns whether a consumer objected — the standard "cancellable hook" idiom on the platform. ## Frequent errors - **`return false` in an `addEventListener` callback does nothing.** The value is discarded. (In a legacy inline `onclick="..."` attribute handler it does cancel the default, which is where the folklore comes from.) - **Cancelling a form's `submit` does not stop validation or clear the form**; it only prevents the navigation/submission. - **Cancelling too early.** On `click` the default action runs after the dispatch completes, so cancelling anywhere in the path works. But you cannot cancel from an asynchronous continuation: after an `await`, the dispatch has finished and the default action has already been taken. ## How to answer State the two axes in one sentence each, give one concrete default action, add the `cancelable` precondition, and finish with `defaultPrevented` as the cooperative alternative to stopping propagation. That last move is what an interviewer at middle level is listening for — it shows you have thought about what your cancellation does to *other people's* handlers.

  • Why does returning false from an addEventListener callback not cancel the default action?
    Because the DOM ignores a listener's return value entirely. The `return false` idiom comes from legacy inline `on*` attribute handlers, where the HTML specification does treat a `false` return as a cancellation. With `addEventListener` the only way to cancel is to call `event.preventDefault()`.
  • How does the code that dispatched a custom event find out whether a listener cancelled it?
    `dispatchEvent()` returns a boolean: `false` if the event was cancelable and some listener called `preventDefault()`, `true` otherwise. The event must be constructed with `{ cancelable: true }` for that to be possible — the platform uses this same mechanism for its own cancellable actions.
  • When would you prefer setting defaultPrevented over calling stopPropagation in a nested component?
    Whenever other listeners on the path have legitimate business with the event — analytics, focus management, outside-click dismissal. Cancelling publishes "this interaction is claimed" while letting everyone still observe it; stopping propagation silently deletes the event for every ancestor, including code you did not write and cannot see.

saying these in an interview costs you the question

  • Saying preventDefault stops the event from bubbling
  • Believing return false cancels in an addEventListener callback
  • Calling preventDefault on a non-cancelable event and expecting an effect
  • Assuming stopPropagation also blocks the browser's default action
  • Trying to preventDefault after an await inside the handler

context