Event bubbling and capturing matter most when nested buttons trigger parents, delegated menus read the wrong node, or stopPropagation hides the real bug. Strong answers explain phase order, target vs currentTarget, and when capture or composedPath are useful.
Use this JavaScript interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.
What is event bubbling and capturing in JavaScript?Frontend interview answer
This JavaScript interview question tests whether you can explain JavaScript event bubbling vs capturing: delegated menus and debug flow, connect it to production trade-offs, and handle common follow-up questions.
- JavaScript event bubbling vs capturing: delegated menus and debug flow explanation without falling back to memorized docs wording
- DOM and Events reasoning, edge cases, and production failure modes
- How you would answer the most likely JavaScript interview follow-up
The Core Idea
When an event (like a click) occurs in the DOM, it does not belong to only one element. It travels through a three-phase path: capture -> target -> bubble. The real production/debug value is understanding why a nested button can trigger its panel and backdrop, why delegated menus sometimes read the wrong node, and why blindly calling stopPropagation() often hides the wrong bug.
Phase | Direction | Triggered On | Listener Option |
|---|---|---|---|
Capturing | Top → Down | From document to target |
|
Target | — | Actual element clicked | Event fires directly on target |
Bubbling | Bottom → Up | From target to document |
|
Example: bubbling in a modal
A modal often contains a clickable panel inside a clickable backdrop. If you click a nested save button, the event still bubbles to the panel and then to the backdrop unless you handle it deliberately.
<div id='backdrop'>
<div id='panel'>
<button id='save'>Save</button>
</div>
</div>
backdrop.addEventListener('click', (event) => {
console.log('backdrop', {
target: event.target.id,
currentTarget: event.currentTarget.id
});
});
panel.addEventListener('click', (event) => {
console.log('panel', {
target: event.target.id,
currentTarget: event.currentTarget.id
});
});
save.addEventListener('click', (event) => {
console.log('button', {
target: event.target.id,
currentTarget: event.currentTarget.id
});
});
Output when you click the save button:
button { target: 'save', currentTarget: 'save' }
panel { target: 'save', currentTarget: 'panel' }
backdrop { target: 'save', currentTarget: 'backdrop' }This is the first follow-up most people miss: event.target is the deepest clicked node, while event.currentTarget is whichever listener is currently running.
Single trace: capture -> target -> bubble
A combined trace is often the fastest way to debug whether the wrong handler is firing or the right handler is firing in the wrong phase.
function trace(label) {
return (event) => {
console.log(label, {
target: event.target.id,
currentTarget: event.currentTarget.id
});
};
}
shell.addEventListener('click', trace('shell capture'), true);
menu.addEventListener('click', trace('menu capture'), true);
item.addEventListener('click', trace('item target'));
menu.addEventListener('click', trace('menu bubble'));
shell.addEventListener('click', trace('shell bubble'));
Expected order for a click on item:
shell capture -> menu capture -> item target -> menu bubble -> shell bubbleThis makes phase order visible and shows why target stays the same while currentTarget changes as the event moves.
Delegated menu/list example
Propagation knowledge matters most when one ancestor listener handles many dynamic children.
menu.addEventListener('click', (event) => {
const actionButton = event.target.closest('[data-action]');
if (!actionButton || !menu.contains(actionButton)) return;
runAction(actionButton.dataset.action);
});
Likely follow-up confusion
event.targetanswers 'what was actually clicked?'event.currentTargetanswers 'which listener is running right now?'- Capture is opt-in; bubbling is default.
- Use
stopPropagation()narrowly, because it can break delegation, analytics, and outside-click handlers higher in the tree. - If a component uses shadow DOM,
composedPath()can reveal the full path more accurately than guessing from one node.
Stopping propagation
You can stop the event from moving further in either direction using:
event.stopPropagation()-> stops it from continuing up or down.
event.stopImmediatePropagation()-> also prevents other handlers on the same element from firing.
Use it as a targeted fix, not as a blanket answer to every propagation bug.
document.getElementById('save').addEventListener('click', (event) => {
event.stopPropagation();
console.log('Only the save button handles this.');
});
Think of event flow like a messenger walking through a building: down the halls to the room (capture), delivering the message in the room (target), then walking back out past the same checkpoints (bubble).
- The event path has three phases: capture -> target -> bubble.
- Bubbling is the default in JavaScript.
event.targetandevent.currentTargetanswer different questions.
- This is core knowledge for event delegation, outside-click logic, and nested UI debugging.
Use this as one explanation rep, then continue with the JavaScript interview questions cluster or a guided prep path.