DOM Interview Questions

DOM questions check whether you understand what a framework is doing on your behalf — events, layout reads and the cleanup you stopped writing by hand.

This topic has 20 curated DOM questions covering event delegation and bubbling, layout thrashing, memory leaks and safe rendering. Below are 8 of them in full, with the same answers you get in the interactive tool.

Practise all 20 DOM questions →

Difficulty breakdown

  • Easy4
  • Medium8
  • Hard7
  • Expert1

Question formats

  • Multiple choice5
  • Write code4
  • Concept3
  • Debugging2
  • Follow-up2
  • Scenario2

What these questions cover

  • events
  • performance
  • forms
  • memory
  • rendering
  • security
  • a11y
  • api
  • attributes
  • bubbling

8 example DOM interview questions

MediumBest practice

You need to measure several elements and then resize them. Why does the obvious loop get slow?

for (const el of items) {
  const h = el.offsetHeight;   // read  — forces layout
  el.style.height = h * 2 + 'px'; // write — invalidates layout
}

Each write invalidates layout, and the next read forces the browser to recompute it immediately to give you an accurate number. Alternating them turns one layout pass into one per item. Batch instead: read every measurement into an array first, then apply every write in a second loop.

Browsers naturally defer layout until they must, so a run of writes costs one recalculation. Interleaving defeats that — this is layout thrashing, and it scales with the number of items, which is why it is invisible with ten rows and janky with five hundred. The read/write split is the fix; `requestAnimationFrame` helps schedule the write phase but does not itself prevent the thrash.

MediumWrite code

Write a "close when the user clicks outside" helper for a dropdown, and say what it must handle

function closeOnOutside(el, onClose) {
  const onPointerDown = (e) => { if (!el.contains(e.target)) onClose(); };
  const onKeyDown = (e) => { if (e.key === 'Escape') onClose(); };
  document.addEventListener('pointerdown', onPointerDown);
  document.addEventListener('keydown', onKeyDown);
  return () => {
    document.removeEventListener('pointerdown', onPointerDown);
    document.removeEventListener('keydown', onKeyDown);
  };
}

Attach a `pointerdown` listener on `document` and close when `!el.contains(event.target)`; add a `keydown` listener for Escape; return a disposer that removes both. Attach them only while the dropdown is open, and ideally on the next tick, so the very click that opened it does not immediately close it.

The three things interviewers look for: `contains` rather than `target === el`, because the click almost always lands on a child; `pointerdown` rather than `click`, so a drag that starts inside and ends outside does not close it; and a disposer, because a dropdown mounted a hundred times leaves a hundred document listeners otherwise. Escape is not optional — a dropdown that can only be dismissed with a mouse is unusable from the keyboard.

EasyConcept

What do UI frameworks actually do for you that direct DOM manipulation does not?

They own the mapping from state to DOM, so you describe what the UI should look like and they work out the minimal set of changes. Doing it by hand means writing the update path for every possible transition — and those paths multiply combinatorially, which is where imperative UI code rots. They also give you a component model with scoped state, a lifecycle with cleanup so subscriptions and listeners are torn down predictably, batched updates so ten state changes cost one paint, and event delegation and cross-browser normalisation for free. None of this is magic and direct DOM work is perfectly appropriate for small or isolated widgets — the argument is entirely about how the complexity grows.

The combinatorial point is the real one: writing updates by hand means handling every transition between states, and the number of transitions grows far faster than the number of states. Declaring the target and letting the framework diff collapses that to one description. What you buy it with is a runtime, a build step and a reconciliation model you now have to understand to debug performance.

HardDebugging

Focus jumps to the top of the page whenever the list refreshes. Diagnose it

function refresh(items) {
  list.innerHTML = '';
  for (const i of items) list.append(renderRow(i));
}

Clearing `innerHTML` destroys the node that currently has focus. When the focused element is removed, focus falls back to `document.body`, which screen readers and keyboard users experience as being thrown to the top of the page mid-task. Fix by not rebuilding wholesale — reconcile so unchanged rows keep their nodes — or, if you must rebuild, record the active element’s identity beforehand and restore focus to its replacement afterwards, announcing the update via a live region rather than relying on focus movement.

This is the DOM-level version of the React key problem, and it is invisible unless you navigate by keyboard.

HardFollow-up

Your event delegation works everywhere except inside a web component. What changed?

Shadow DOM retargets events as they cross the boundary: to a listener outside the shadow root, `event.target` is reported as the HOST element, not the inner node you expected, so a `closest()` check against an inner selector finds nothing. Events also only cross the boundary at all if the component dispatched them with `composed: true`. To handle inner elements you either listen inside the shadow root, use `event.composedPath()[0]` to see the true origin where that is permitted, or — best — have the component dispatch a semantic custom event (`composed: true, bubbles: true`) that describes what happened, so outside code never depends on its internals.

Retargeting is deliberate encapsulation rather than a quirk; the custom-event answer is the one that respects it.

MediumMultiple choice

Why build a list in a `DocumentFragment` before appending?

Building off-document means one insertion into the live tree instead of N, and one style/layout invalidation instead of N. It is NOT "one layout instead of N layouts": modern browsers already batch style and layout, recomputing once before the next paint rather than on every mutation. The benefit is therefore real but modest — and it matters most when the loop also READS layout (`offsetHeight`, `getBoundingClientRect`) between mutations, because each read forces a synchronous recalculation that batching would otherwise have avoided.

HardPredict the output

Which of these injected payloads actually executes?

el.innerHTML = '<script>alert(1)<\/script>';   // A
el.innerHTML = '<img src=x onerror=alert(1)>';  // B

Only B. A script inserted via `innerHTML` is parsed but NOT executed — the HTML spec marks such scripts non-executable. B runs because the image fails to load and the `onerror` attribute handler fires.

This is why "innerHTML is safe because scripts do not run" is a dangerous half-truth: the real vectors are event-handler attributes, `javascript:` URLs, and elements like `<iframe srcdoc>` — which is exactly what a sanitiser’s allow-list has to cover.

ExpertScenario

A third-party script mutates DOM your framework owns, causing random crashes. How do you contain it?

First establish what it touches, with a `MutationObserver` logging changes outside your own roots so you have evidence rather than suspicion. The crashes happen because your framework holds references to nodes that were moved or removed underneath it, so the fix is ownership: give the third party a container your framework never renders into, and render your own tree elsewhere. If it insists on injecting at `body` level, isolate it in a sandboxed iframe and communicate via postMessage. Where neither is possible, mark the region so your framework treats it as opaque, and make your own mount points resilient — re-query rather than caching nodes across ticks. Finally, treat it as a vendor issue with evidence attached; containment is mitigation, not a fix.

The framing that matters is ownership of DOM regions — two systems mutating the same subtree is a contract violation, not a bug to patch around indefinitely.

The remaining 12 DOM questions — plus mock interviews, spaced revision and progress tracking — are in the free interview prep workspace.

Related interview topics