A virtual DOM is just plain objects describing what the UI should look like — cheap to create and compare — which a renderer then turns into real DOM nodes. This is the heart of React (and every framework like it): createElement(type, props, …children) builds the tree of objects (that's what JSX compiles to), and render(vnode) walks it to produce actual elements. Implementing both demystifies the whole idea.
Implement createElement(type, props, ...children) → a { type, props, children } vnode, and render(vnode) → a real DOM node. Text/number vnodes become text nodes; object vnodes become elements with their props applied and children rendered.
function createElement(type, props, ...children) {
// returns { type, props, children }
}
function render(vnode) {
// returns a DOM Node
}
const tree = createElement('div', { className: 'card' },
createElement('h1', null, 'Title'),
createElement('p', null, 'Body'),
);
render(tree).outerHTML;
// '<div class="card"><h1>Title</h1><p>Body</p></div>'
{ type, props: props || {}, children: children.flat() }. Rest params collect children; flat() handles a child that's an array.string/number vnode → document.createTextNode(String(v)); an object vnode → document.createElement(type), apply props, append rendered children.className → the class attribute; an onX function prop → addEventListener('x', fn); anything else → setAttribute.render calls itself on each child and appends the result, building the tree depth-first.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.