Skip to solution
mediumFrontend

What is the difference between shadow DOM and virtual DOM?

43 views
01

Understand 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.

domarchitectureconcepts
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for React interviews — no prior Web Components knowledge assumed. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. The object shapes and the encapsulation test in section 3 were

Solution ready — 2 min read

Classified // press E to declassify

04

Explore the playground snippets

Inspecting a React element as an object, and a shadow root hiding its contents
Run Playground
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>
  );
}
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 71 of 119 decoded in the React.js track. One more won't hurt.

Back to track