Predict the Output: 10 Event Delegation and DOM Event Flow Questions Every Front-End Developer Should Practise
In technical interviews for front-end engineering roles, interviewers routinely test candidates on browser runtime behavior, DOM architecture, and event handling. While many candidates know how to attach an event prop in a component library, interviewers often present standalone JavaScript snippets manipulating DOM nodes to evaluate whether you genuinely understand the browser's event dispatch pipeline.
These questions appear frequently in technical screenings, live debugging sessions, and core JavaScript technical rounds. Interviewers use them to assess:
- Event Dispatch Phases: Traversal through capturing (
CAPTURING_PHASE), at-target (AT_TARGET), and bubbling (BUBBLING_PHASE). - Target Resolution: The distinction between
event.target(origin element clicked) andevent.currentTarget(element executing the handler). - Propagation Control: How
stopPropagation()differs fromstopImmediatePropagation()andpreventDefault(). - Listener Options: Runtime behavior of
{ capture: true },{ once: true }, and{ passive: true }. - Event Delegation Mechanics: Handling nested children with
Element.prototype.closest(), and knowing which events bubble.
This article targets developers with 0–5 years of experience preparing for front-end technical rounds. Practise predicting the exact console output for each snippet before reviewing the step-by-step trace and interviewer commentary.
Question Index
| # | Question / Snippet Focus | Target Concept | Difficulty |
|---|---|---|---|
| 1 | Standard Event Bubbling vs Target | Bubbling order and target vs currentTarget | Junior |
| 2 | Capturing Phase Listener Execution Order | Capture flags (capture: true) vs default bubbling | Junior-Mid |
| 3 | Execution Order on the Exact Target Node | Listeners attached directly to target (AT_TARGET) | Mid |
| 4 | stopPropagation() vs Outer Ancestor Listeners | Halting upstream propagation across phases | Mid |
| 5 | stopImmediatePropagation() vs Peer Listeners | Halting same-element peer listener execution | Mid |
| 6 | Event Delegation with Nested Child Elements | Target resolution on icon/span inside a button | Mid |
| 7 | One-Time Listeners ({ once: true }) Across Multi-Clicks | Listener auto-removal semantics on repeat events | Mid |
| 8 | Capture vs Bubble Combined with stopPropagation() | Stopping propagation during the capturing phase | Mid-Senior |
| 9 | Non-Bubbling Events (focus vs focusin) in Delegation | Propagation boundaries of non-bubbling events | Mid-Senior |
| 10 | preventDefault() Impact on Propagation and Defaults | Default action cancellation vs event flow | Mid-Senior |
Q1. Standard Event Bubbling: Target vs CurrentTarget
Difficulty: Junior
Target Concept: Event bubbling phase order and identifying event.target vs event.currentTarget.
Code Snippet
// Setup DOM structure:
// <div id="parent">
// <button id="child">Click Me</button>
// </div>
const parent = document.getElementById('parent');
const child = document.getElementById('child');
parent.addEventListener('click', (e) => {
console.log('Parent:', e.target.id, e.currentTarget.id);
});
child.addEventListener('click', (e) => {
console.log('Child:', e.target.id, e.currentTarget.id);
});
// The user clicks directly on the child button:
child.click();
What is the exact console output?
Predicted Output
Child: child child
Parent: child parent
Step-by-Step Execution Trace
child.click()initiates synchronous DOM event dispatch fromwindowdown to#childand back up towindow.- Capturing Phase: No capture listeners are registered (
capture: falseby default), so no callbacks execute during descent. - Target Phase (
AT_TARGET): The event reaches#child. The click listener onchildfires.e.targetis the origin element clicked (child).e.currentTargetis the element whose listener is executing (child).- Logs:
Child: child child.
- Bubbling Phase (
BUBBLING_PHASE): The event bubbles up to#parent. The click listener onparentfires.e.targetremains the origin element (child).e.currentTargetis the listener host element (parent).- Logs:
Parent: child parent.
Spoken Explanation & Common Mistakes
- Spoken Summary: "Because listeners are registered without capture flags, they execute in bubbling order—child first, then parent. Throughout propagation,
event.targetreferences the origin element clicked (child), whileevent.currentTargetreferences the element hosting the active listener." - Common Mistake: Assuming
event.targetchanges to#parentwhen the parent's listener executes, confusingtargetwithcurrentTarget.
Q2. Capturing Phase Order vs Default Bubbling
Difficulty: Junior-Mid
Target Concept: How { capture: true } flips invocation sequence relative to bubbling listeners.
Code Snippet
// Setup DOM structure:
// <div id="outer">
// <div id="inner">
// <button id="btn">Action</button>
// </div>
// </div>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const btn = document.getElementById('btn');
outer.addEventListener('click', () => console.log('1: Outer Bubble'), false);
inner.addEventListener('click', () => console.log('2: Inner Capture'), true);
outer.addEventListener('click', () => console.log('3: Outer Capture'), true);
btn.addEventListener('click', () => console.log('4: Button Bubble'), false);
inner.addEventListener('click', () => console.log('5: Inner Bubble'), false);
// The user clicks the button:
btn.click();
What is the exact console output?
Predicted Output
3: Outer Capture
2: Inner Capture
4: Button Bubble
5: Inner Bubble
1: Outer Bubble
Step-by-Step Execution Trace
- Event dispatch traverses three standard phases: Capturing (Phase 1), At-Target (Phase 2), and Bubbling (Phase 3).
- Capturing Phase: Descends from root down towards
#btn.- On
#outer: Capture listener runs. Logs3: Outer Capture. - On
#inner: Capture listener runs. Logs2: Inner Capture.
- On
- At-Target Phase: Reaches
#btn.- Button listener runs. Logs
4: Button Bubble.
- Button listener runs. Logs
- Bubbling Phase: Ascends from
#btnback to root.- On
#inner: Bubbling listener runs. Logs5: Inner Bubble. - On
#outer: Bubbling listener runs. Logs1: Outer Bubble.
- On
Spoken Explanation & Common Mistakes
- Spoken Summary: "Capture listeners execute top-down as the event travels down the DOM tree (
outertheninner), followed by the target listener onbtn, followed by bubbling listeners executing bottom-up (innerthenouter)." - Common Mistake: Listing
2: Inner Capturebefore3: Outer Capturebased on registration order. During capturing, DOM tree depth dictates sequence, not registration order.
Q3. Execution Order on the Exact Target Node
Difficulty: Mid
Target Concept: Listener ordering during eventPhase === Event.AT_TARGET in the modern DOM standard.
Code Snippet
// Setup DOM structure:
// <div id="container">
// <button id="targetBtn">Click</button>
// </div>
const targetBtn = document.getElementById('targetBtn');
targetBtn.addEventListener('click', () => console.log('A: Target Bubble'), false);
targetBtn.addEventListener('click', () => console.log('B: Target Capture'), true);
targetBtn.addEventListener('click', () => console.log('C: Target Bubble Second'), false);
targetBtn.click();
What is the exact console output?
Predicted Output
A: Target Bubble
B: Target Capture
C: Target Bubble Second
Step-by-Step Execution Trace
- When the event reaches the target element itself (
eventPhase === Event.AT_TARGET), capturing and bubbling phase distinctions do not reorder listeners. - In the modern WHATWG DOM Standard, listeners registered directly on the target element execute in strict registration order, regardless of their
captureflags. - First listener registered on
targetBtn:A: Target Bubble(registered withfalse). Executes first. - Second listener registered on
targetBtn:B: Target Capture(registered withtrue). Executes second. - Third listener registered on
targetBtn:C: Target Bubble Second(registered withfalse). Executes third.
Spoken Explanation & Common Mistakes
- Spoken Summary: "At the target phase (
AT_TARGET), modern browsers execute listeners registered on the target node in strict registration order. Thecaptureflag only prioritizes ancestor nodes during descent." - Common Mistake: Assuming
B: Target Capturemust execute beforeA: Target Bubbleon the target node because capture precedes bubbling in general.
Q4. stopPropagation() vs Outer Ancestor Listeners
Difficulty: Mid
Target Concept: How event.stopPropagation() halts upward bubbling while allowing peer listeners on the current node to finish.
Code Snippet
// Setup DOM structure:
// <div id="card">
// <button id="deleteBtn">Delete</button>
// </div>
const card = document.getElementById('card');
const deleteBtn = document.getElementById('deleteBtn');
card.addEventListener('click', () => {
console.log('Card clicked: navigate to details');
});
deleteBtn.addEventListener('click', (e) => {
console.log('1: Delete handler A');
e.stopPropagation();
});
deleteBtn.addEventListener('click', () => {
console.log('2: Delete handler B');
});
deleteBtn.click();
What is the exact console output?
Predicted Output
1: Delete handler A
2: Delete handler B
Step-by-Step Execution Trace
- The click event initiates on
deleteBtn. - First listener on
deleteBtnexecutes: logs1: Delete handler A, then callse.stopPropagation(). e.stopPropagation()sets an internal flag preventing the event from propagating to subsequent elements along the event path.- However,
stopPropagation()does not stop other listeners registered on the same element from executing. - Second listener on
deleteBtnexecutes: logs2: Delete handler B. - Bubbling towards
#cardis prevented by the stopped flag. The#cardlistener never runs.
Spoken Explanation & Common Mistakes
- Spoken Summary: "
event.stopPropagation()prevents the event from bubbling up to parent ancestors like#card, but allows remaining listeners registered on the current element (deleteBtn) to run." - Common Mistake: Assuming
stopPropagation()immediately terminates all subsequent listeners on the current element, omitting2: Delete handler B.
Q5. stopImmediatePropagation() vs Peer Listeners
Difficulty: Mid
Target Concept: Complete termination of all remaining listeners, including peers on the same element.
Code Snippet
// Setup DOM structure:
// <button id="multiBtn">Run</button>
const btn = document.getElementById('multiBtn');
btn.addEventListener('click', () => {
console.log('First listener running');
});
btn.addEventListener('click', (e) => {
console.log('Second listener: stopping immediately');
e.stopImmediatePropagation();
});
btn.addEventListener('click', () => {
console.log('Third listener running');
});
btn.click();
What is the exact console output?
Predicted Output
First listener running
Second listener: stopping immediately
Step-by-Step Execution Trace
- The click event dispatches to
btn. - The browser iterates through the registered listener list for
btnin FIFO registration order. - Listener 1 runs: logs
First listener running. - Listener 2 runs: logs
Second listener: stopping immediately, and callse.stopImmediatePropagation(). e.stopImmediatePropagation()halts further listener iteration on the current element and cancels ancestor propagation.- Listener 3 is skipped entirely.
Spoken Explanation & Common Mistakes
- Spoken Summary: "Unlike
stopPropagation(),stopImmediatePropagation()halts execution immediately, blocking subsequent listeners registered on the current element as well as preventing propagation to ancestors." - Common Mistake: Believing
stopImmediatePropagation()only behaves likestopPropagation(), expecting Listener 3 to execute.
Q6. Event Delegation with Nested Child Elements
Difficulty: Mid
Target Concept: How nested child elements change event.target and handling delegation with closest().
Code Snippet
// Setup DOM structure:
// <ul id="list">
// <li id="item-1">
// <button class="delete-btn" data-id="1">
// <span class="icon">🗑️</span>
// <span class="text">Remove</span>
// </button>
// </li>
// </ul>
const list = document.getElementById('list');
list.addEventListener('click', (e) => {
console.log('Clicked node tag:', e.target.tagName);
const btn = e.target.closest('.delete-btn');
if (btn) {
console.log('Button found with id:', btn.dataset.id);
} else {
console.log('No button match');
}
});
// User clicks directly on the icon inside the button:
const icon = document.querySelector('.icon');
icon.click();
What is the exact console output?
Predicted Output
Clicked node tag: SPAN
Button found with id: 1
Step-by-Step Execution Trace
- The click originates directly on the
<span class="icon">element. - The event bubbles through the hierarchy:
span.icon->button.delete-btn->li#item-1->ul#list. - The delegated click listener on
ul#listreceives the event. e.targetrefers strictly to the origin element clicked (span), soe.target.tagNamelogs'SPAN'.e.target.closest('.delete-btn')ascends from the span to find the nearest matching ancestor, resolving to<button class="delete-btn" data-id="1">.btn.dataset.idreads'1', loggingButton found with id: 1.
Spoken Explanation & Common Mistakes
- Spoken Summary: "Clicking a nested child makes
e.targetthat child node (theSPAN), not the button. Robust event delegation usese.target.closest('selector')rather than checkinge.target.tagNamedirectly." - Common Mistake: Expecting
e.targetto automatically be the<button>because the button is the interactive control.
Q7. One-Time Listeners ({ once: true }) Across Multi-Clicks
Difficulty: Mid
Target Concept: The lifecycle and auto-removal mechanics of { once: true }.
Code Snippet
// Setup DOM structure:
// <div id="panel">
// <button id="trigger">Click</button>
// </div>
const panel = document.getElementById('panel');
const trigger = document.getElementById('trigger');
let count = 0;
trigger.addEventListener('click', () => {
count += 1;
console.log(`Trigger: count is ${count}`);
}, { once: true });
panel.addEventListener('click', () => {
count += 10;
console.log(`Panel: count is ${count}`);
});
// First click:
trigger.click();
// Second click:
trigger.click();
What is the exact console output?
Predicted Output
Trigger: count is 1
Panel: count is 11
Panel: count is 21
Step-by-Step Execution Trace
- First Click:
- Reaches
trigger: listener incrementscountto 1. LogsTrigger: count is 1. - Browser automatically removes this listener from
trigger. - Event bubbles to
panel: listener incrementscountby 10 to 11. LogsPanel: count is 11.
- Reaches
- Second Click:
- Reaches
trigger: no listeners remain ontrigger. - Event bubbles to
panel: listener incrementscountby 10 to 21. LogsPanel: count is 21.
- Reaches
Spoken Explanation & Common Mistakes
- Spoken Summary: "The
{ once: true }option removes the handler after its first invocation. On subsequent clicks, only the persistent parent listener onpanelexecutes." - Common Mistake: Believing
{ once: true }stops bubbling on the first click, or removes listeners from the entire DOM hierarchy.
Q8. Capture vs Bubble Combined with stopPropagation()
Difficulty: Mid-Senior
Target Concept: Halting an event during the downward capturing phase before it reaches the target.
Code Snippet
// Setup DOM structure:
// <div id="grandparent">
// <div id="parent">
// <button id="btn">Click</button>
// </div>
// </div>
const grandparent = document.getElementById('grandparent');
const parent = document.getElementById('parent');
const btn = document.getElementById('btn');
grandparent.addEventListener('click', () => {
console.log('1: Grandparent Capture');
}, true);
parent.addEventListener('click', (e) => {
console.log('2: Parent Capture with stop');
e.stopPropagation();
}, true);
btn.addEventListener('click', () => {
console.log('3: Button Target');
});
parent.addEventListener('click', () => {
console.log('4: Parent Bubble');
});
grandparent.addEventListener('click', () => {
console.log('5: Grandparent Bubble');
});
btn.click();
What is the exact console output?
Predicted Output
1: Grandparent Capture
2: Parent Capture with stop
Step-by-Step Execution Trace
- Event begins capture phase downwards towards
btn. - Reaches
grandparentduring capture: logs1: Grandparent Capture. - Reaches
parentduring capture: logs2: Parent Capture with stop, then callse.stopPropagation(). - Propagation halts immediately along the event path.
- Neither
btn(target) nor any bubbling listeners onparentorgrandparentever execute.
Spoken Explanation & Common Mistakes
- Spoken Summary: "Calling
stopPropagation()during capture intercepts the event before it ever reaches the target. Propagation stops, preventing the target and bubbling handlers from firing." - Common Mistake: Expecting the target button listener to fire under the mistaken assumption that
stopPropagation()only halts bubbling.
Q9. Non-Bubbling Events (focus vs focusin) in Delegation
Difficulty: Mid-Senior
Target Concept: Non-bubbling events (focus, blur) vs bubbling counterparts (focusin, focusout), and using capture mode for delegation.
Code Snippet
// Setup DOM structure:
// <form id="loginForm">
// <input id="username" type="text" />
// </form>
const form = document.getElementById('loginForm');
const input = document.getElementById('username');
// Listener A: 'focus' with default bubbling (false)
form.addEventListener('focus', () => {
console.log('A: Form focus (bubble)');
}, false);
// Listener B: 'focus' with capturing (true)
form.addEventListener('focus', () => {
console.log('B: Form focus (capture)');
}, true);
// Listener C: 'focusin' with default bubbling (false)
form.addEventListener('focusin', () => {
console.log('C: Form focusin (bubble)');
}, false);
// Focus the input:
input.focus();
What is the exact console output?
Predicted Output
B: Form focus (capture)
C: Form focusin (bubble)
Step-by-Step Execution Trace
input.focus()dispatchesfocus(bubbles: false) andfocusin(bubbles: true).- For
focus:- Descends during capture: Listener B (
capture: true) catches it on#loginForm. LogsB: Form focus (capture). - Reaches target
#username. - Does not bubble: Listener A (
capture: false) never executes.
- Descends during capture: Listener B (
- For
focusin:- Fires and bubbles up to
#loginForm. Listener C (capture: false) catches it. LogsC: Form focusin (bubble).
- Fires and bubbles up to
Spoken Explanation & Common Mistakes
- Spoken Summary: "Native
focusandblurdo not bubble. To delegate them on an ancestor, you must capture them (capture: true) or use bubbling equivalentsfocusinandfocusout." - Common Mistake: Assuming all DOM events bubble, expecting Listener A to execute.
Q10. preventDefault() Impact on Propagation vs Default Actions
Difficulty: Mid-Senior
Target Concept: Disentangling default action cancellation (preventDefault()) from event propagation (stopPropagation()).
Code Snippet
// Setup DOM structure:
// <div id="wrapper">
// <a id="link" href="https://example.com">Visit</a>
// </div>
const wrapper = document.getElementById('wrapper');
const link = document.getElementById('link');
wrapper.addEventListener('click', (e) => {
console.log('Wrapper clicked, defaultPrevented:', e.defaultPrevented);
});
link.addEventListener('click', (e) => {
console.log('Link clicked: canceling default navigation');
e.preventDefault();
});
link.click();
What is the exact console output?
Predicted Output
Link clicked: canceling default navigation
Wrapper clicked, defaultPrevented: true
Step-by-Step Execution Trace
- Synthetic click dispatches to
link. - Target Phase: Listener on
linklogsLink clicked: canceling default navigationand callse.preventDefault(). preventDefault()sets the canceled flag (defaultPrevented = true) but does not halt propagation.- Bubbling Phase: Event bubbles to
wrapper. Listener logsWrapper clicked, defaultPrevented: true. - Browser suppresses native hyperlink navigation after listeners finish.
Spoken Explanation & Common Mistakes
- Spoken Summary: "
event.preventDefault()cancels the browser default action (e.g. navigation) but has no effect on propagation. The event continues bubbling, and ancestors can inspectevent.defaultPrevented." - Common Mistake: Confusing
preventDefault()withstopPropagation(), assuming default cancellation halts propagation.
Preparation Checklist
To master DOM events and event delegation before your next front-end interview, review this actionable checklist:
- Distinguish
targetvscurrentTarget:targetis where the user clicked (origin node), whilecurrentTargetis the element to which the listener is attached. - Memorize the 3 phases: Capturing (
1), At-target (2), and Bubbling (3). By default,addEventListener(type, fn)only listens during phases 2 and 3. - Know propagation methods:
stopPropagation()stops ancestor travel;stopImmediatePropagation()also stops other listeners on the same element;preventDefault()only prevents default browser behaviors. - Audit non-bubbling events: Events that do not bubble include
focus,blur,mouseenter,mouseleave,load, andunload. Usefocusin/focusoutor{ capture: true }when delegating them. - Use
Element.prototype.closest()in delegation: When implementing delegated event handlers for elements with child icons or nested markup, always traverse up usinge.target.closest(selector). - Understand
{ once: true }and{ passive: true }: Know how{ once: true }cleans up handlers after first run, and how{ passive: true }promises never to callpreventDefault(), boosting scroll performance. - Trace complex code on a whiteboard: Practise drawing the DOM tree and tracing arrows down during capture and up during bubble before answering output-prediction questions.
Conclusion
Understanding the browser's native event propagation pipeline is a key differentiator in technical interviews. By recognizing how capturing, at-target, and bubbling phases interact with listener options and delegation strategies, you can confidently explain not just what code outputs, but why the browser runtime behaves that way. Practise writing out event traces systematically during interviews to demonstrate senior-level mastery of the DOM.