Skip to main content

What is the DOM?

DOM (Document Object Model) is a live tree of objects the browser builds from an HTML document in memory, letting JavaScript read, modify, add, or remove nodes without reloading the page.

Theory

TL;DR

  • The DOM is what the browser builds from your HTML and keeps in memory as a mutable object tree, not a static file
  • Analogy: HTML is the printed recipe; the DOM is the chef's live workspace where ingredients can be swapped or added mid-cook
  • Main difference: HTML is parsed once and lives on disk; the DOM lives in memory and changes constantly
  • Use raw DOM APIs for small scripts and extensions; switch to React or Preact when updates become frequent or complex

Quick Example

html
<!DOCTYPE html> <html> <body> <p id="demo">Original text</p> <script> const para = document.getElementById('demo'); para.textContent = 'DOM changed this!'; // No reload needed console.log(para.nodeName); // "P" </script> </body> </html>

getElementById returns a live node object. Setting textContent immediately triggers a repaint in the browser's rendering pipeline. That's the whole loop.

HTML vs the DOM

HTML is a static markup file. The browser parses it once and builds the DOM tree in memory. After that, the original file is irrelevant: what the user sees is the DOM. That's why JavaScript can change page content without touching the source file.

The DOM gives you standardized node objects (ElementNode, TextNode, CommentNode) with methods like appendChild(). Calling those methods tells the browser to update what's on screen automatically.

Node Types

Every item in the DOM tree is a node. Four types come up constantly:

  • Element node (<div>, <p>, etc.) - nodeType: 1
  • Text node (actual text content inside tags) - nodeType: 3
  • Comment node (HTML comments) - nodeType: 8
  • Document node (the root, document) - nodeType: 9

This matters when iterating. childNodes returns all types including text nodes from whitespace between tags. children returns element nodes only. Mix them up and you'll hit a Cannot set properties of undefined error on a whitespace node.

When to Use

  • Form validation or class toggling - raw DOM APIs are fine
  • Browser extensions or bookmarklets - no framework available anyway
  • Quick UI prototypes - skip the build setup entirely
  • Heavy or frequent updates, like rendering 1000 list items on user input - switch to a virtual DOM library like React; its reconciler batches DOM changes and minimizes reflows

How the Browser Handles DOM Mutations

Chrome's Blink engine stores the DOM as C++ Node objects exposed to JavaScript via WebIDL bindings. When you call querySelector(), V8 traverses the tree depth-first in O(n) worst case. A mutation like appendChild() marks the affected branch as dirty, which queues a style recalculation, layout pass (reflow), paint, and compositing. Grouping mutations inside requestAnimationFrame collapses those passes into one and is how you stay at 60fps with DOM-heavy code.

Node.js has no DOM. For testing HTML parsing in Node, use jsdom.

Common Mistakes

Mistake 1: innerHTML with user input

javascript
// Wrong: parses and executes markup element.innerHTML = userInput; // <script>alert('XSS')</script> runs // Correct: treats everything as plain text element.textContent = userInput;

In code reviews, I see innerHTML = userInput far more often than I'd like. It usually appears in deadline code where someone needed it to work fast and didn't think about where the input came from. Use textContent or createTextNode() for anything user-supplied.

Mistake 2: Not checking for null

javascript
// Wrong const el = document.getElementById('nonexistent'); // null el.textContent = 'hi'; // TypeError: Cannot set property // Correct if (el) el.textContent = 'hi';

getElementById returns null when nothing matches. One unchecked result crashes the whole script.

Mistake 3: Mutating a live collection during iteration

javascript
// Wrong: getElementsByClassName returns a live HTMLCollection const items = document.getElementsByClassName('old-item'); Array.prototype.forEach.call(items, item => item.remove()); // skips every other item // Correct: convert to a static array first Array.from(document.getElementsByClassName('old-item')).forEach( item => item.remove() );

getElementsByClassName and getElementsByTagName return live collections. Removing an element shifts the indices, so the loop skips the next sibling every time. querySelectorAll returns a static snapshot and does not have this problem.

Mistake 4: children vs childNodes

javascript
// Works: children contains element nodes only parent.children[0].style.color = 'red'; // May crash: childNodes[0] could be a whitespace TextNode parent.childNodes[0].style.color = 'red'; // Cannot set properties of undefined

Mistake 5: Forgetting to remove event listeners before removing the node

javascript
const btn = document.createElement('button'); btn.addEventListener('click', handler); document.body.appendChild(btn); btn.remove(); // listener stays in memory // Correct btn.removeEventListener('click', handler); btn.remove();

In single-page apps where elements are created and destroyed frequently, orphaned listeners add up into a real memory leak.

Real-World Usage

  • React: ReactDOM.render() translates JSX into actual DOM mutations via createElement and appendChild
  • jQuery (legacy): $(el).find() wraps querySelectorAll with cross-browser compatibility
  • Puppeteer: page.$eval() runs JavaScript against the live DOM for end-to-end tests
  • WordPress / SSR apps: server renders HTML, then client JavaScript hydrates the DOM for interactivity

Follow-Up Questions

Q: What is the difference between textContent and innerHTML?
A: textContent sets plain text and escapes everything. innerHTML parses the string as markup and will execute scripts if you're not careful. For any user-supplied data, always use textContent.

Q: How does querySelector traverse the tree?
A: Depth-first, pre-order, starting from the document root. It stops at the first match, which makes it faster than querySelectorAll when you only need one element.

Q: What is the difference between the real DOM and the virtual DOM?
A: The real DOM is the actual browser tree. The virtual DOM is a plain JavaScript object that mirrors it. React diffs the old and new virtual trees, then applies only the minimal set of changes to the real DOM.

Q: What node types exist?
A: The main ones are Element (nodeType 1), Text (nodeType 3), Comment (nodeType 8), and Document (nodeType 9). Check with node.nodeType.

Q (senior level): Walk through what Blink does when you call appendChild().
A: The node is inserted into the C++ tree. That marks the affected branch dirty, scheduling a style recalculation, then layout (reflow to compute geometry), then paint (draw pixels), then compositing. Grouping multiple mutations before the next frame using requestAnimationFrame merges those passes into one, which is how DOM-heavy code stays at 60fps.

Examples

Basic: Changing text content

html
<!DOCTYPE html> <html> <body> <p id="demo">Original text</p> <script> const para = document.getElementById('demo'); para.textContent = 'Updated by JS'; // triggers repaint console.log(para.nodeName); // "P" </script> </body> </html>

Grabbing a node by ID and updating its text is the most common DOM operation. No page reload, no framework needed.

Intermediate: Building a live to-do list

html
<!DOCTYPE html> <html> <body> <ul id="todos"></ul> <input id="task" placeholder="Task name" /> <button id="add">Add</button> <script> const ul = document.getElementById('todos'); const input = document.getElementById('task'); document.getElementById('add').addEventListener('click', () => { const li = document.createElement('li'); li.textContent = input.value; // textContent keeps this XSS-safe ul.appendChild(li); input.value = ''; }); </script> </body> </html>

This is essentially what TodoMVC does in its vanilla JS implementation. createElement builds the node, appendChild inserts it, and textContent keeps it safe. React's useState + JSX does the same thing under the hood, just with a diff step before touching the real DOM.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?