Build: Mini Virtual DOM
Introduction
A mini virtual DOM challenge asks you to model UI as plain JavaScript objects, render that tree, and compute differences between two trees. The interview goal is not to rebuild React; it is to demonstrate tree representation, recursion, escaping, props comparison, and patch generation.
This version stays worker-safe by rendering to strings and producing plain-object patches instead of touching the real DOM.
Why This Matters
Virtual DOM questions test whether you understand the ideas behind modern UI libraries: declarative trees, pure render output, reconciliation, keys, and patching. A compact implementation gives you vocabulary for discussing React without hand-waving.
Theory
Virtual node shape
A virtual node can be represented as { type, props, children }. Text nodes are just strings or numbers. The helper h(type, props, ...children) flattens children and removes empty values.
Rendering
Rendering to a string is a pure version of DOM rendering. Escape text and attribute values to prevent HTML injection. Boolean attributes can be rendered by name when true and omitted when false.
Diffing
A basic diff compares node pairs at the same path. If one side is missing, create or remove. If primitive text differs, emit a text patch. If element type differs, replace. If type matches, diff props and recurse into children.
Follow-up: keys
This simple diff compares children by index. Real libraries use keys to detect moves and preserve component state across reorderings. Mentioning keyed diffing is an important follow-up in interviews.
Visual Diagrams
h calls | v virtual tree | +--> renderToString -> HTML string | +--> diff old tree vs new tree -> patch list
The tree is plain data, so rendering and diffing can be pure functions.
Code Examples
Complete mini virtual DOM
The implementation is intentionally pure: no document, no real DOM, and no framework dependencies.
Playground
Press Run to execute the code and see output here.
Output Prediction
Predict the output #1
1function h(type, props) {2 var children = [];3 for (var index = 2; index < arguments.length; index += 1) {4 children.push(arguments[index]);5 }6 return { type: type, props: props || {}, children: children };7}8 9function diff(oldNode, newNode, path) {10 var currentPath = path || 'root';11 12 if (!oldNode) {13 return [{ type: 'CREATE', path: currentPath }];14 }15 16 if (!newNode) {17 return [{ type: 'REMOVE', path: currentPath }];18 }19 20 var oldIsText = typeof oldNode === 'string' || typeof oldNode === 'number';21 var newIsText = typeof newNode === 'string' || typeof newNode === 'number';22 23 if (oldIsText || newIsText) {24 return oldNode === newNode ? [] : [{ type: 'TEXT', path: currentPath }];25 }26 27 if (oldNode.type !== newNode.type) {28 return [{ type: 'REPLACE', path: currentPath }];29 }30 31 var patches = [];32 var oldClass = oldNode.props.class;33 var newClass = newNode.props.class;34 35 if (oldClass !== newClass) {36 patches.push({ type: 'PROPS', path: currentPath });37 }38 39 var max = Math.max(oldNode.children.length, newNode.children.length);40 for (var index = 0; index < max; index += 1) {41 patches = patches.concat(diff(oldNode.children[index], newNode.children[index], currentPath + '.' + index));42 }43 44 return patches;45}46 47var oldTree = h('ul', {}, h('li', { class: 'item' }, 'A'));48var newTree = h('ul', {}, h('li', { class: 'active' }, 'A'), h('li', {}, 'B'));49console.log(diff(oldTree, newTree).map(function (patch) {50 return patch.type + ':' + patch.path;51}).join('|'));Coding Exercises
Implement a mini virtual DOM
HardBuild a pure mini virtual DOM.
Requirements:
h(type, props, ...children)returns a virtual node and flattens nested child arrays.- Ignore
null,undefined, andfalsechildren. renderToString(node)returns escaped HTML.- Boolean true props render as attributes without values.
- False, null, and undefined props are omitted.
diff(oldNode, newNode)returns patches with paths.- Patches should cover create, remove, replace, text, and props changes.
Constraints:
- Do not use the real DOM.
- Do not use a framework.
- Keep the implementation pure and runnable in a worker.
Interview Questions
1Why do real virtual DOM libraries use keys?
Keys let the diff algorithm match logical children across reorders, insertions, and deletions. Without keys, index-based diffing may replace or mutate the wrong child and can lose component state.
Follow-ups
- How would you implement keyed child diffing?
- How does React Fiber change scheduling?
2Why escape text and attribute values in `renderToString`?
Rendering user-controlled strings without escaping can create HTML injection or XSS vulnerabilities. Escaping turns special characters into safe entities.
Quiz
1. What patch should be emitted when two virtual nodes have different `type` values at the same path?
Summary
- A virtual DOM tree is plain data: type, props, and children.
- `renderToString` should escape text and attribute values.
- A basic diff handles create, remove, replace, text, and prop patches.
- Index-based child diffing is simple; keyed diffing is the production follow-up.
Cheat Sheet
Mini VDOM checklist
h(type, props, ...children)builds plain nodes.- Flatten child arrays; ignore null, undefined, false.
- Text nodes can be strings or numbers.
- Escape HTML in text and attributes.
- Render boolean true attributes by name.
- Diff paths like
root.0.1. - Cases: create, remove, text, replace, props.
- Mention keys for reorder-aware reconciliation.