JavaScript · DOM & Events

DOM & Events Interview Questions
With Answers & Code Examples

7 carefully curated DOM & Events interview questions with working code examples and real interview gotchas.

Practice Interactively →← All Categories
7 questions1 beginner4 core2 advanced
Q1Core

Explain event delegation and why it is useful.

💡 Hint: One listener on parent; use event.target to identify the child

Attach ONE listener to a parent instead of many listeners on children. Works because events bubble up the DOM.

// ❌ Inefficient
document.querySelectorAll('li').forEach(li =>
  li.addEventListener('click', handleClick)
);

// ✅ Event delegation
document.querySelector('ul').addEventListener('click', (e) => {
  if (e.target.matches('li')) handleClick(e.target);
});

Benefits: fewer listeners = less memory, works for dynamically added elements, cleaner teardown.

Practice this question →
Q2Beginner

What is the difference between event.stopPropagation() and event.preventDefault()?

💡 Hint: stopPropagation=stop bubbling; preventDefault=cancel browser default action
  • stopPropagation() — stops event from bubbling to parent elements
  • preventDefault() — cancels browser default (link navigation, form submit) but still bubbles
  • stopImmediatePropagation() — stops bubbling + prevents other listeners on same element
link.addEventListener('click', (e) => {
  e.preventDefault();    // don't navigate
  e.stopPropagation();   // don't bubble up
  doSomething();
});
Practice this question →
Q3Core

What is the difference between event bubbling and event capturing?

💡 Hint: 3 phases: capture (down), target, bubble (up) — addEventListener default is bubble

When an event fires, it passes through 3 phases in the DOM tree:

  1. Capture phase — event travels DOWN: window → document → ... → target
  2. Target phase — event is at the element that was clicked/triggered
  3. Bubble phase — event travels UP: target → ... → document → window
// Capture phase listener (3rd arg = true, or { capture: true })
document.body.addEventListener('click', () => console.log('body capture'), true);

// Bubble phase listener (default)
document.body.addEventListener('click', () => console.log('body bubble'));
button.addEventListener('click', () => console.log('button'));

// Click the button:
// body capture (capturing, going down)
// button       (at target)
// body bubble  (bubbling, going up)

// Stop bubbling
button.addEventListener('click', (e) => {
  e.stopPropagation(); // stops bubble — body bubble won't fire
  // e.stopImmediatePropagation() — also blocks same-element listeners
});

Events that don't bubble: focus, blur, scroll, mouseenter, mouseleave — use focusin/focusout for delegation instead.

💡 Event delegation relies on bubbling — one listener on the parent handles events from all children. This is more efficient than attaching listeners to each child.
Practice this question →
Q4Core

How do you create and dispatch Custom Events?

💡 Hint: new CustomEvent(name, { detail, bubbles }) — dispatch with element.dispatchEvent()

Custom Events let you create your own event types for loosely-coupled component communication.

// Create
const loginEvent = new CustomEvent('user:login', {
  detail: { userId: 42, name: 'Alice' }, // payload — any data
  bubbles: true,     // will bubble up the DOM
  cancelable: true   // can be preventDefault'd
});

// Dispatch
document.dispatchEvent(loginEvent);
// or: specificElement.dispatchEvent(loginEvent);

// Listen
document.addEventListener('user:login', (e) => {
  console.log(e.detail.name); // 'Alice'
  console.log(e.type);        // 'user:login'
});

// Real-world: decoupled component communication
class Cart {
  addItem(item) {
    this.items.push(item);
    window.dispatchEvent(new CustomEvent('cart:updated', {
      detail: { items: this.items, count: this.items.length }
    }));
  }
}

// Navbar listens independently
window.addEventListener('cart:updated', ({ detail }) => {
  cartBadge.textContent = detail.count;
});
💡 Namespace your events with colons (user:login, cart:updated) to avoid collisions with native events. This is a lightweight pub/sub without a library.
Practice this question →
Q5Advanced

What is MutationObserver and when do you use it?

💡 Hint: Watch for DOM changes asynchronously — replaces deprecated Mutation Events

MutationObserver watches for changes to the DOM tree and fires a callback when changes occur — batched and asynchronous.

const observer = new MutationObserver((mutations, obs) => {
  mutations.forEach(mutation => {
    if (mutation.type === 'childList') {
      console.log('Added:', mutation.addedNodes);
      console.log('Removed:', mutation.removedNodes);
    }
    if (mutation.type === 'attributes') {
      console.log('Attr changed:', mutation.attributeName, 'on', mutation.target);
    }
    if (mutation.type === 'characterData') {
      console.log('Text changed');
    }
  });
});

observer.observe(document.getElementById('app'), {
  childList: true,     // watch add/remove of child nodes
  attributes: true,    // watch attribute changes
  characterData: true, // watch text content changes
  subtree: true,       // observe all descendants too
});

observer.disconnect(); // stop observing

Use cases: Lazy loading, detecting when third-party code modifies the DOM, building virtual DOM diffing, accessibility announcements.

💡 MutationObserver replaced deprecated synchronous Mutation Events (DOMNodeInserted etc.) which were slow and could cause infinite loops. MutationObserver batches changes for performance.
Practice this question →
Q6Advanced

What is IntersectionObserver and how do you use it for lazy loading?

💡 Hint: Detect when elements enter the viewport — no scroll listener, no layout thrashing
// Lazy loading images
const observer = new IntersectionObserver((entries, obs) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src; // load real image
      obs.unobserve(img);        // stop watching this one
    }
  });
}, {
  root: null,          // null = viewport
  rootMargin: '200px', // trigger 200px BEFORE element is visible
  threshold: 0.1       // fire when 10% of element is visible
});

// Watch all lazy images
document.querySelectorAll('img[data-src]').forEach(img => {
  observer.observe(img);
});

// Infinite scroll
const sentinel = document.querySelector('.load-more');
new IntersectionObserver(([entry]) => {
  if (entry.isIntersecting) loadNextPage();
}).observe(sentinel);

// entry properties:
// entry.isIntersecting — true/false
// entry.intersectionRatio — 0 to 1
// entry.boundingClientRect — element position
// entry.target — the observed element
💡 IntersectionObserver is async and non-blocking — no layout thrashing from getBoundingClientRect() in scroll handlers. Far more performant than scroll events.
Practice this question →
Q7Core

What is the difference between async and defer for script loading?

💡 Hint: Both avoid blocking HTML parsing — async executes ASAP, defer after full parse

By default, <script> blocks HTML parsing while downloading and executing.

AttributeDownloadExecutes whenOrder
None (default)Blocks HTMLImmediately, blocksIn order
asyncParallelAs soon as downloadedNOT guaranteed
deferParallelAfter HTML fully parsedIn document order
<!-- Blocks parsing ❌ (put in <head>) -->
<script src="app.js"></script>

<!-- Parallel download, executes ASAP when downloaded -->
<!-- ORDER NOT GUARANTEED — analytics.js might run before vendor.js -->
<script async src="analytics.js"></script>

<!-- Parallel download, runs after HTML parsed, IN ORDER ✅ -->
<script defer src="vendor.js"></script>
<script defer src="app.js"></script>
<!-- app.js always runs after vendor.js -->

When to use:

  • defer — most app scripts (DOM-dependent, order-dependent)
  • async — completely independent scripts (analytics, ads)
💡 type="module" scripts are deferred by default. In modern apps using bundlers, you usually put one deferred script tag pointing at the bundle.
Practice this question →

Other JavaScript Interview Topics

Core JSFunctionsAsync JSObjectsArrays'this' KeywordError HandlingModern JSPerformanceBrowser APIs

Ready to practice DOM & Events?

Get AI feedback on your answers, predict code output, and fix real bugs.

Start Free Practice →