Explain the DOM tree structure
The Short Answer
The DOM (Document Object Model) is a tree data structure that represents an HTML document. Every HTML element becomes a node in the tree, with the document object as the root. Parent-child relationships in the tree mirror the nesting of HTML tags. The browser parses HTML into this tree, and JavaScript uses it to read, modify, add, or remove elements on the page.
From HTML to Tree
When the browser receives an HTML file, it parses the markup character by character and builds the DOM tree. Each opening tag creates a node, nesting creates parent-child relationships, and text between tags becomes text nodes. The resulting tree is what JavaScript interacts with — not the HTML source text.
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
</head>
<body>
<h1>Hello</h1>
<p>Welcome to <a href="/about">my site</a></p>
</body>
</html>
This HTML produces a tree where document is the root, html is its child, head and body are children of html, and so on. The <a> tag is a child of <p>, which is a child of <body>. Text like "Hello" and "Welcome to" are text nodes — leaf nodes that can't have children.
- document
- html → head → title → "My Page" (text node)
- html → body → h1 → "Hello" (text node)
- html → body → p → a (nested element)
Node Types
Not everything in the DOM tree is an element. The tree contains different types of nodes, each with a numeric nodeType value. Understanding these types matters because methods like childNodes return all node types (including whitespace text nodes), while children returns only element nodes.
| Node Type | nodeType value | Example | Description |
|---|---|---|---|
| Element | 1 | <div>, <p>, <a> | HTML tags — the most common type |
| Text | 3 | "Hello world" | Text content between tags (includes whitespace) |
| Comment | 8 | <!-- note --> | HTML comments |
| Document | 9 | document | The root node of the entire tree |
| DocumentFragment | 11 | createDocumentFragment() | Lightweight container for batch operations |
Navigating the Tree
Every node in the DOM tree has properties that let you traverse to related nodes — parent, children, and siblings. There are two sets of navigation properties: one that includes all node types (including text and comment nodes), and one that only includes element nodes. In practice, you almost always want the element-only versions.
const paragraph = document.querySelector('p')!;
// Parent
paragraph.parentNode; // Any node type
paragraph.parentElement; // Element only (null if parent is document)
// Children
paragraph.childNodes; // All nodes (includes text, comments, whitespace)
paragraph.children; // Elements only (HTMLCollection)
paragraph.firstChild; // First node (might be whitespace text)
paragraph.firstElementChild; // First element child
paragraph.lastElementChild; // Last element child
// Siblings
paragraph.nextSibling; // Next node (might be text)
paragraph.nextElementSibling; // Next element
paragraph.previousElementSibling; // Previous element
// Common gotcha: childNodes includes whitespace text nodes
// <div>\n <p>Hi</p>\n</div>
// div.childNodes.length === 3 (text, p, text)
// div.children.length === 1 (just p)
The whitespace gotcha trips up many developers. Newlines and spaces between tags create text nodes, so childNodes often has more items than you expect. Use children, firstElementChild, and nextElementSibling to skip text nodes.
The Tree Is Live
The DOM tree is a live representation — when you modify it with JavaScript, the page updates immediately (after the browser's next render). And some collections returned by DOM methods are live too, meaning they automatically reflect changes to the document without you needing to re-query.
// HTMLCollection (from getElementsByClassName) is LIVE
const items = document.getElementsByClassName('item');
console.log(items.length); // 3
document.querySelector('.item')!.classList.remove('item');
console.log(items.length); // 2 — automatically updated!
// NodeList (from querySelectorAll) is STATIC
const staticItems = document.querySelectorAll('.item');
console.log(staticItems.length); // 2
document.querySelector('.item')!.classList.remove('item');
console.log(staticItems.length); // 2 — still 2, it's a snapshot
Live vs static collections
getElementsByClassName and getElementsByTagName return live HTMLCollections. querySelectorAll returns a static NodeList (a snapshot). Live collections can cause bugs in loops if you're modifying the DOM while iterating.
DOM vs HTML vs Render Tree
It's important to distinguish the DOM tree from the HTML source and the render tree. The HTML is just text — the parser converts it into the DOM tree. The render tree is a separate structure the browser builds for painting — it excludes invisible elements (like display: none) and includes pseudo-elements that aren't in the DOM.
| Concept | What it is | Includes hidden elements? | Includes pseudo-elements? |
|---|---|---|---|
| HTML source | Text markup — input to the parser | Yes | No (they're CSS) |
| DOM tree | In-memory tree of nodes — what JS interacts with | Yes (display:none is in DOM) | No |
| Render tree | Layout tree for painting — what the user sees | No (skips display:none) | Yes (::before, ::after) |
Why Interviewers Ask This
This question tests foundational web knowledge. Interviewers want to see that you understand the DOM is a tree (not a flat list or the HTML itself), know the different node types and how to navigate between them, understand the difference between live and static collections, and can distinguish the DOM from the render tree. It's the foundation for understanding how frameworks like React work (virtual DOM diffing) and why DOM manipulation has performance implications.
Quick Revision Cheat Sheet
Data structure: Tree — nodes with parent/child/sibling relationships
Root node: document (nodeType 9)
Common node types: Element (1), Text (3), Comment (8), Document (9)
Element-only navigation: children, firstElementChild, nextElementSibling
All-node navigation: childNodes, firstChild, nextSibling (includes text/comments)
Live vs static: getElementsBy* → live | querySelectorAll → static snapshot