What is MutationObserver?

Web APIsJavaScript

The short answer

MutationObserver watches a DOM node and runs a callback when its children, attributes, or text content change. It replaced the old Mutation Events, which were deprecated because they fired synchronously on every change and hurt performance. MutationObserver instead batches changes and delivers them asynchronously, so observing the DOM stays cheap.

How it works

const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
console.log('Children changed', mutation.addedNodes);
}
}
});
observer.observe(targetNode, {
childList: true, // watch direct children being added or removed
attributes: true, // watch attribute changes
subtree: true, // also watch all descendants
});

The callback receives an array of MutationRecord objects. Each record tells you what changed: its type (childList, attributes, or characterData), the target node, and details like addedNodes, removedNodes, or attributeName.

Config options

The second argument to observe controls what you listen for:

  • childList: direct children added or removed
  • attributes: attribute changes
  • characterData: text content changes
  • subtree: extend any of the above to all descendants
  • attributeFilter: only watch specific attributes, such as ['class', 'data-state']
  • attributeOldValue / characterDataOldValue: include the previous value in each record

Waiting for an element to appear

A common use is acting on DOM that a third party script inserts later. You can wrap the observer in a promise that resolves once a matching element exists:

function waitForElement(selector) {
return new Promise((resolve) => {
const existing = document.querySelector(selector);
if (existing) return resolve(existing);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect();
resolve(el);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
});
}

When you are done, observer.disconnect() stops all watching. observer.takeRecords() returns any pending records that have not been delivered yet, which is useful right before disconnecting.

Interview Tip

Scope the observer as tightly as you can. Observing document.body with subtree: true means every change anywhere in the page runs your callback, which can get expensive. Observe the smallest node that contains what you care about, use attributeFilter when you only need certain attributes, and always disconnect when the work is done.

Why interviewers ask this

MutationObserver comes up when you need to integrate with DOM you do not control: third party widgets, legacy code, or content injected after load. Interviewers want to see that you know it replaced the deprecated Mutation Events for performance reasons, that you can configure it for the change you care about, and that you disconnect it to avoid leaks. It is the standard answer to "how would you react to a DOM change you did not trigger."