Document Object Model and Event Handling Questions
Comprehensive coverage of using JavaScript to build interactive web interfaces by manipulating the Document Object Model and responding to user and browser events. Core skills include element selection and traversal, reading and updating content and attributes, creating and removing nodes, and making efficient, performance conscious updates to minimize layout and paint costs. Event handling topics cover registering and removing event listeners, the structure of event objects, the difference between event target and current target, propagation models including capturing and bubbling, stopping propagation and preventing default behavior, event delegation for scalable handlers, and handling common event types such as click, input, change, submit, keyboard, pointer, touch, and drag and drop. Also included are patterns for state management in vanilla JavaScript so that user interactions update the interface predictably, controlled versus uncontrolled form patterns in component frameworks such as React, and cross component event flows and architecture for more senior assessments. Familiarity with browser application programming interfaces commonly used with interactive features such as Intersection Observer, fetch for network requests, and local storage is expected. Asynchronous JavaScript fundamentals are part of this topic, including the event loop, callbacks, promises, and the async and await syntax. Defensive and performance practices are emphasized, including debouncing and throttling, batching and minimizing Document Object Model mutations, avoiding memory leaks by cleaning up listeners and references, handling dynamically inserted content, and accessibility concerns such as keyboard navigation, focus management, and form validation.
EasyTechnical
18 practiced
Explain event propagation phases in the browser: capturing, target, and bubbling. For each phase, describe when an event listener runs and how you can register a listener to run in the capturing phase. Also explain the effect of calling event.stopPropagation() in a capturing listener.
Sample Answer
**Definition — three phases**- Capturing (trickle-down): event travels from window → document → root → down to the target element.- Target: event reaches the target element; listeners on the target run (both capture and bubble listeners can run here; capture handlers run before bubble handlers on the target).- Bubbling (bubble-up): event travels back up from target → ancestors → document → window, invoking bubble-phase listeners.**When a listener runs**- A listener registered with capture mode runs during the capturing phase as the event moves down toward the target (or on the target before bubble listeners).- A normal (non-capture) listener runs during the bubbling phase when the event bubbles back up (unless the event doesn’t bubble).- On the target: capture listeners fire first, then target listeners registered without capture (bubbling) run.**How to register a capturing listener**
**Effect of event.stopPropagation() in a capturing listener**- Calling event.stopPropagation() inside a capturing listener prevents the event from continuing its journey: it stops further capturing listeners deeper (closer to target) and also prevents any subsequent target and bubbling-phase listeners from running.- It does not prevent other listeners already registered on the same node and same phase from running; to stop those as well, use event.stopImmediatePropagation().
MediumTechnical
22 practiced
Implement a throttle function in JavaScript with signature: function throttle(fn, wait) { /* ... */ } that ensures fn runs at most once every wait milliseconds. Show an example where throttle is preferable to debounce (e.g., window resize or scroll handlers).
Sample Answer
**Approach**Use a timestamp + trailing call option: allow immediate call if enough time passed, otherwise ignore until wait elapses. Keep it simple for common UI handlers.**Implementation**
javascript
function throttle(fn, wait) {
let last = 0;
let timeout = null;
return function throttled(...args) {
const now = Date.now();
const remaining = wait - (now - last);
const context = this;
if (remaining <= 0) {
if (timeout) { clearTimeout(timeout); timeout = null; }
last = now;
fn.apply(context, args);
} else if (!timeout) {
// schedule trailing call for the remaining time
timeout = setTimeout(() => {
last = Date.now();
timeout = null;
fn.apply(context, args);
}, remaining);
}
};
}
**Explanation & complexity**- Time-based check with optional trailing invocation ensures at most one call per wait ms.- O(1) space; O(1) per call.- Edge cases: preserve this and args, clear timeout on immediate call, wait = 0.**When throttle > debounce**Use throttle for scroll or resize where you need periodic updates (e.g., infinite-scroll position checks, updating a sticky header) so the handler runs at steady intervals. Debounce would delay until the user stops, causing less responsive UI for continuous actions.
EasyTechnical
25 practiced
You have a form that should not submit when client-side validation fails. Provide a small JavaScript example (vanilla JS) that listens for the form's submit event, prevents the default submission when validation fails, shows a validation message, and gives an accessible focus target for the user. Mention which event methods you used and why.
Sample Answer
**Approach (brief)** Listen for the form's "submit" event, call event.preventDefault() to stop submission when validation fails, show an inline message, move focus to the first invalid control and set an accessible description.
html
<form id="signup">
<label for="email">Email</label>
<input id="email" name="email" type="email" required>
<div id="email-error" class="error" aria-live="polite"></div>
<label for="password">Password</label>
<input id="password" name="password" type="password" required minlength="8">
<div id="password-error" class="error" aria-live="polite"></div>
<button type="submit">Submit</button>
</form>
<script>
const form = document.getElementById('signup');
form.addEventListener('submit', function (event) {
// Use built-in validity first
const email = form.email;
const password = form.password;
// clear previous messages
document.getElementById('email-error').textContent = '';
document.getElementById('password-error').textContent = '';
let firstInvalid = null;
if (!email.checkValidity()) {
// prevent submission and show message
event.preventDefault(); // stops the default form submit
document.getElementById('email-error').textContent =
email.validationMessage || 'Enter a valid email.';
email.setAttribute('aria-describedby', 'email-error');
firstInvalid = firstInvalid || email;
}
if (!password.checkValidity()) {
event.preventDefault();
document.getElementById('password-error').textContent =
password.validationMessage || 'Password required (min 8).';
password.setAttribute('aria-describedby', 'password-error');
firstInvalid = firstInvalid || password;
}
if (firstInvalid) {
// move focus to the first invalid field for keyboard & screen readers
firstInvalid.focus();
// optional: select() for text inputs to help correction
if (typeof firstInvalid.select === 'function') firstInvalid.select();
}
});
</script>
**Key methods and why**- event.preventDefault(): stops the browser from submitting when validation fails.- checkValidity() and validationMessage: use built-in constraints before custom logic.- focus(): provides an accessible target so keyboard and screen-reader users land where correction is needed.- aria-describedby + aria-live: expose the error text to assistive tech.**Edge cases**- Server-side validation still required.- For multiple errors consider a summary region with role="alert" and a link list to inputs.
HardTechnical
22 practiced
You are building a reusable UI library in vanilla JS that creates components and attaches event listeners. Describe patterns to ensure components clean up event listeners and DOM references when removed, considering both explicit teardown APIs and automatic detection. Mention trade-offs and integration points for frameworks.
Sample Answer
**Clarify goal**Keep library components free of memory leaks by removing DOM refs and event listeners when components are removed — support both explicit teardown and automatic detection.**Patterns**- Explicit teardown API - Expose mount/unmount or destroy method that removes listeners, cancels timers, nulls refs. - Example:
javascript
// simple component factory with explicit destroy
function Card(root) {
const onClick = () => console.log('clicked');
root.addEventListener('click', onClick);
return {
destroy() {
root.removeEventListener('click', onClick);
// clear other refs or intervals
}
};
}
- Centralized listener registry - Track attached handlers in a Map/WeakMap and remove them in destroy.- Event delegation - Attach high-level listeners (document or container) to reduce per-component handlers; remove only when last component unmounted.- Automatic detection - Use MutationObserver to detect node removal and call cleanup for known nodes (store cleanup function in WeakMap keyed by node). - Use WeakRef + FinalizationRegistry (where supported) to run cleanup when object is GC'd — note non-deterministic timing and limited browser support.
javascript
const cleanupMap = new WeakMap();
const mo = new MutationObserver(records => { /* detect removed nodes and call cleanupMap.get(node) */ });
- AbortController pattern - Pass an AbortSignal to async tasks and listeners so they can cancel on teardown.- Web Components integration - Implement connectedCallback/disconnectedCallback to auto-clean.**Trade-offs**- Explicit API: deterministic and simple; requires discipline from consumers.- MutationObserver/FinalizationRegistry: automatic but adds complexity, possible perf cost, and is non-deterministic.- Event delegation: fewer handlers but more logic for event routing.**Framework integration**- React/Vue: expose mount/unmount hooks; provide small adapter that calls component.destroy() in useEffect cleanups or beforeUnmount.- Offer thin wrappers: e.g., React hook useLibraryComponent(nodeRef) that mounts and ensures cleanup.**Recommendation**Provide explicit destroy + WeakMap registry + optional MutationObserver fallback. Document integration adapters for popular frameworks and prefer AbortController for async work.
EasyTechnical
24 practiced
Given nested HTML and a click inside an inner element, write a concise vanilla-JS snippet that finds the nearest ancestor element with attribute data-role="widget" using a DOM API method designed for this purpose. Explain why it is preferred over manual parent walking.
Sample Answer
**Approach — brief**Use Element.closest(selector) which walks up the DOM and returns the nearest ancestor (including self) matching a selector. It's concise, fast and uses native optimized engine instead of manual loops.**Code**
javascript
// inside an event handler where `event` is the click event
const widget = event.target.closest('[data-role="widget"]');
// widget is either the matching ancestor element or null
**Why prefer closest()**- Native, implemented in browser engines (better perf and maintainability).- Handles the "include self" case automatically.- One-liner readable intent vs error-prone manual parent traversal.- Avoids writing and testing loop/stop conditions (document root, null checks).**Edge cases**- Returns null if no match; verify before use.- Works on Element nodes; ensure event.target may be a text node in some browsers — using event.target.closest is safe because target will be an Element for click events in modern browsers.
Unlock Full Question Bank
Get access to hundreds of Document Object Model and Event Handling interview questions and detailed answers.