All topics
DOMadvanced

MutationObserver and Reacting to DOM Changes

A browser API for observing and reacting to changes in the DOM tree — added/removed nodes, attribute changes, text changes — asynchronously and efficiently.

MutationObserver reports changes to the DOM asynchronously, in batches.

MutationObserver is like a security camera with motion-triggered recording pointed at a specific room (the target node): instead of a guard manually walking back to check the room every few seconds (polling), the camera silently watches and delivers a batched summary of everything that happened the moment there's a natural pause to check the footage (microtask), rather than interrupting everything the instant a single item moves.

Key Concepts

1
    const obs = new MutationObserver((mutations) => {
      for (const m of mutations) {
        if (m.type === 'childList') handleAdded(m.addedNodes);
      }
    });
2
    obs.observe(target, {
      childList: true,      // children added or removed
      subtree: true,        // and in all descendants
      attributes: true,
      attributeFilter: ['class'],
      characterData: true
    });
3
    obs.disconnect();
4
What it replaced. Mutation events fired synchronously on every change and made pages crawl. They were deprecated for that reason.
5
MutationObserver batches changes and delivers them as a microtask, so a hundred insertions produce one callback.
MutationObserver
6
Where it is genuinely needed.Reacting to markup you do not control — a third-party widget, an embedded editor, a CMS-injected block.
7
Enforcing something across dynamic content — attaching behaviour to elements that appear later, when delegation cannot help because it is not an event.
8
Detecting when an element is finally rendered, in a codebase where you cannot hook the render.
9
The infinite-loop trap.
    const obs = new MutationObserver(() => {
      el.classList.add('seen');      // triggers the observer again
    });
    obs.observe(el, { attributes: true });
10
Disconnect before mutating and reconnect afterwards, or filter so your own change is ignored.
11
Scope it narrowly. subtree: true on document.body with everything enabled reports every change on the page, and the callback then runs constantly.
subtree: truedocument.body
12
    attributeFilter: ['class']     // only this attribute
    observe(section, ...)          // only this part of the tree
13
attributeOldValue and characterDataOldValue must be requested explicitly if you need the previous value.
attributeOldValuecharacterDataOldValue
14
When not to use it. If you own the code that changes the DOM, call your function there — an observer is indirection with a cost. And for visibility, use IntersectionObserver; for size, ResizeObserver. Each is cheaper than watching mutations and inferring.
IntersectionObserverResizeObserver
15
What the interviewer is probing.1. "What replaced mutation events and why?" Probing: the history. Stalls: "Nothing." *Moves up:* MutationObserver — the old events fired synchronously on every change and were a serious performance problem.
16
2. "When do the callbacks fire?" Probing: the batching. Stalls: "Immediately." Moves up: asynchronously as a microtask, with records batched, which is what makes it cheap.
17
3. "What must you configure?" Probing: the options. Stalls: "Just the element." Moves up: which mutations to watch — childList, attributes, subtree, characterData — since nothing is observed by default.
18
4. "When would you actually need it?" Probing: honest use cases. Stalls: "Often." *Moves up:* reacting to DOM you do not control, such as third-party widgets or injected content — your own framework already tells you.