MutationObserver

MutationObserver asynchronously reports selected changes to a DOM subtree without requiring event handlers on every individual mutation. It is useful for reacting to DOM insertions, removals, attribute changes, and text changes when the code making those changes is not under your control.

MutationObserver watches a DOM node or subtree and queues records describing matching mutations. You create an observer with a callback, then call `observe()` with a target node and options describing which mutation types matter.

Common options include `childList`, `attributes`, `characterData`, and `subtree`. For attribute changes, `attributeFilter` can restrict observation to named attributes, while `attributeOldValue` can request the previous attribute value. For character-data changes, `characterDataOldValue` can request the previous text value.

The callback receives a list of MutationRecord objects. The records describe what changed, such as added or removed nodes, the affected target, and changed attributes. Multiple DOM changes can be delivered together, so code should not assume one callback invocation corresponds to exactly one mutation.

MutationObserver callbacks are asynchronous: DOM changes are observed and records are delivered after the current JavaScript execution completes rather than immediately at the mutation site. This makes the API different from directly wrapping every operation that modifies the DOM.

Use `disconnect()` to stop observing, and `takeRecords()` when you need to retrieve currently queued records synchronously. Observing large subtrees unnecessarily can generate substantial callback work, so narrow the target and options when possible.

Key points

Interview questions

What problem does MutationObserver solve?

MutationObserver lets JavaScript react when selected parts of the DOM change. Instead of adding custom hooks to every operation that might modify the DOM, code can observe a target and receive mutation records. It can detect child-node changes, attribute changes, and character-data changes. Delivery is asynchronous rather than occurring inline with the DOM operation.

How do you configure a MutationObserver?

First create an observer with a callback, then call `observe()` with a target node and an options object. The options determine whether to watch child-list changes, attributes, character data, or descendants through `subtree`. Attribute filters and old-value options can further constrain or enrich the records. The configuration should be as narrow as the application allows.

Are MutationObserver callbacks synchronous?

No. Matching mutations are queued and the observer callback runs asynchronously after the relevant JavaScript work has completed. Multiple mutations can therefore be represented by multiple records delivered together. Code should inspect the records rather than assuming the callback corresponds to one immediate DOM operation.

How would you avoid performance problems with MutationObserver?

I would observe the smallest practical target and enable only the mutation types the feature needs. I would avoid broad `subtree` observation when a narrower target is sufficient, and keep callback processing lightweight. If the callback needs expensive work, I would consider batching or deferring that work rather than repeatedly performing costly operations. I would also disconnect observers when their lifecycle ends.

What is the difference between `disconnect()` and `takeRecords()`?

`disconnect()` stops the observer from observing future mutations and discards any queued mutation records that have not yet been delivered. `takeRecords()` instead returns the currently queued records immediately and clears that queue. They therefore solve different problems: one controls observation, while the other retrieves pending records. An application can use both when explicitly managing an observer's lifecycle.