What is MutationObserver?
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 removedattributes: attribute changescharacterData: text content changessubtree: extend any of the above to all descendantsattributeFilter: 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."