Interview answer drill

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

HighIntermediateJavascript
Interview focus

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
Practice more JavaScript interview questions
Interview quick answer

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.

Full interview answer

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

addEventListener('click', handler, true)

Target

Actual element clicked

Event fires directly on target

Bubbling

Bottom → Up

From target to document

addEventListener('click', handler) (default)

The three phases of DOM event propagation.

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.

HTML
<div id='backdrop'>
  <div id='panel'>
    <button id='save'>Save</button>
  </div>
</div>
                  
JAVASCRIPT
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.

JAVASCRIPT
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 bubble

This 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.

JAVASCRIPT
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.target answers 'what was actually clicked?'
  • event.currentTarget answers '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.

JAVASCRIPT
document.getElementById('save').addEventListener('click', (event) => {
  event.stopPropagation();
  console.log('Only the save button handles this.');
});
                  
Still so complicated?

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).

Summary
  • The event path has three phases: capture -> target -> bubble.
  • Bubbling is the default in JavaScript.
  • event.target and event.currentTarget answer different questions.
  • This is core knowledge for event delegation, outside-click logic, and nested UI debugging.
Similar questions
Guides
Preparing for interviews?

Use this as one explanation rep, then continue with the JavaScript interview questions cluster or a guided prep path.