Skip to the solution
MediumFrontend6 min read

What is the difference between shadow DOM and virtual DOM?

43 views

The problem

Question presented to candidate: "Shadow DOM and virtual DOM β€” are they related?"

What a strong answer should cover:

  • No. They solve unrelated problems and the shared word is a coincidence of naming. Saying that first is the right move.
  • Virtual DOM is a React concept: a lightweight tree of plain JavaScript objects describing what the UI should look like, which React diffs against the previous tree to compute minimal DOM updates. It never exists in the browser spec.
  • Shadow DOM is a browser standard, part of Web Components: a genuinely separate DOM subtree attached to an element, with real encapsulation β€” its nodes are not reachable from the outer document and its styles do not leak in either direction.
  • Virtual DOM is about efficient updates; shadow DOM is about isolation.
  • A React element is not a DOM node β€” it is an object with type, props and key.
  • They can coexist: you can render a React tree into a shadow root, and React will happily reconcile inside it. Event delegation needs care, since React attaches listeners at the root container.
  • The honest nuance: virtual DOM is not inherently faster than direct DOM manipulation β€” it is faster than naive re-rendering, and it buys a declarative programming model.

Clarifying questions expected:

  • "Do you mean the concepts, or are you asking whether React uses shadow DOM?" β€” React does not, by default.

Code / implementation expected: No. This is a definitional comparison.

  • dom
  • architecture
  • concepts

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 8 sections

Try it in the playground

Open any example in the playground to run and change it.

ReactInspecting a React element as an object, and a shadow root hiding its contents
import { useState, useEffect, useRef } from "react";

export default function App() {
  const [out, setOut] = useState([]);
  const hostRef = useRef(null);
  const shadowRef = useRef(null);

  // Attach a real shadow root once, on mount.
  useEffect(() => {
    if (hostRef.current && !shadowRef.current) {
      const shadow = hostRef.current.attachShadow({ mode: "open" });
      shadow.innerHTML =
        '<style>p { color: crimson; font-family: system-ui; }</style>' +
        '<p id="inside-shadow">I live inside the shadow root</p>';
      shadowRef.current = shadow;
    }
  }, []);

  const inspectElement = () => {
    // A React element β€” created, never rendered. It is just an object.
    const el = <div className="box">hello</div>;
    setOut([
      "typeof element:  " + typeof el,
      "keys:            " + JSON.stringify(Object.keys(el)),
      "element.type:    " + JSON.stringify(el.type),
      "element.props:   " + JSON.stringify(el.props),
      "is a DOM node?   " + (typeof el.appendChild === "function"),
      "",
      "That is the virtual DOM: a description, not a node.",
    ]);
  };

  const inspectShadow = () => {
    const host = hostRef.current;
    const shadow = shadowRef.current;
    setOut([
      "document.getElementById('inside-shadow'): " + (document.getElementById("inside-shadow") ? "found" : "NOT FOUND"),
      "shadowRoot.getElementById('inside-shadow'): " + (shadow && shadow.getElementById("inside-shadow") ? "found" : "NOT FOUND"),
      "host.textContent: " + JSON.stringify(host.textContent),
      "host.children.length: " + host.children.length,
      "",
      "The paragraph is visible on screen and unreachable from the document.",
      "That is encapsulation enforced by the browser.",
    ]);
  };

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6, maxWidth: 560 }}>
      <div style={{ display: "flex", gap: 8, marginBottom: 12 }}>
        <button onClick={inspectElement}>inspect a React element</button>
        <button onClick={inspectShadow}>inspect the shadow root</button>
      </div>

      {/* The host element. Its shadow content renders but is not its children. */}
      <div
        ref={hostRef}
        style={{ border: "1px dashed #aaa", borderRadius: 8, padding: 10, marginBottom: 12 }}
      />

      <pre style={{ background: "#f6f6f8", padding: 10, borderRadius: 8, fontSize: 12, minHeight: 140, whiteSpace: "pre-wrap" }}>
{out.length ? out.join("\n") : "press a button"}
      </pre>

      <p style={{ fontSize: 13, color: "#666" }}>
        The crimson paragraph above is styled by CSS that exists only inside the
        shadow root β€” it cannot affect the rest of this page, and this page
        cannot style it. Meanwhile the React element is a plain object that has
        never been near the browser. Two entirely different ideas.
      </p>
    </div>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 71 of 119 in React.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All React.js questions
Up nextHard

What causes stale closures in React hooks, and how do you fix them correctly?

Continue